<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.3.0">


  <link rel="apple-touch-icon" sizes="180x180" href="/yuwanzi.io/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/yuwanzi.io/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/yuwanzi.io/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/yuwanzi.io/images/logo.svg" color="#222">

<link rel="stylesheet" href="/yuwanzi.io/css/main.css">



<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.1/css/all.min.css">
  <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/animate.css@3.1.1/animate.min.css">

<script class="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"suyuhuan.gitee.io","root":"/yuwanzi.io/","images":"/yuwanzi.io/images","scheme":"Muse","version":"8.2.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12},"copycode":false,"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"Suche...","empty":"We didn't find any results for the search: ${query}","hits_time":"${hits} results found in ${time} ms","hits":"${hits} results found"}};
  </script>
<meta name="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
<meta property="og:type" content="website">
<meta property="og:title" content="玉丸子 | Blog">
<meta property="og:url" content="https://suyuhuan.gitee.io/yuwanzi.io/index.html">
<meta property="og:site_name" content="玉丸子 | Blog">
<meta property="og:description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
<meta property="og:locale">
<meta property="article:author" content="玉丸子">
<meta name="twitter:card" content="summary">


<link rel="canonical" href="https://suyuhuan.gitee.io/yuwanzi.io/">


<script class="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : true,
    isPost : false,
    lang   : 'zh'
  };
</script>
<title>玉丸子 | Blog</title>
  




  <noscript>
  <style>
  body { margin-top: 2rem; }

  .use-motion .menu-item,
  .use-motion .sidebar,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header {
    visibility: visible;
  }

  .use-motion .header,
  .use-motion .site-brand-container .toggle,
  .use-motion .footer { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle,
  .use-motion .custom-logo-image {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line {
    transform: scaleX(1);
  }

  .search-pop-overlay, .sidebar-nav { display: none; }
  .sidebar-panel { display: block; }
  </style>
</noscript>

<link rel="alternate" href="/yuwanzi.io/atom.xml" title="玉丸子 | Blog" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="Navigationsleiste an/ausschalten" role="button">
    </div>
  </div>

  <div class="site-meta">

    <a href="/yuwanzi.io/" class="brand" rel="start">
      <i class="logo-line"></i>
      <h1 class="site-title">玉丸子 | Blog</h1>
      <i class="logo-line"></i>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>







</div>
        
  
  <div class="toggle sidebar-toggle" role="button">
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
  </div>

  <aside class="sidebar">

    <div class="sidebar-inner sidebar-overview-active">
      <ul class="sidebar-nav">
        <li class="sidebar-nav-toc">
          Inhaltsverzeichnis
        </li>
        <li class="sidebar-nav-overview">
          Übersicht
        </li>
      </ul>

      <div class="sidebar-panel-container">
        <!--noindex-->
        <div class="post-toc-wrap sidebar-panel">
        </div>
        <!--/noindex-->

        <div class="site-overview-wrap sidebar-panel">
          <div class="site-author site-overview-item animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">玉丸子</p>
  <div class="site-description" itemprop="description">这里是玉丸子的个人博客,与你一起发现更大的世界。</div>
</div>
<div class="site-state-wrap site-overview-item animated">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/yuwanzi.io/archives">
          <span class="site-state-item-count">68</span>
          <span class="site-state-item-name">Artikel</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/yuwanzi.io/categories/">
        <span class="site-state-item-count">39</span>
        <span class="site-state-item-name">Kategorien</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/yuwanzi.io/tags/">
        <span class="site-state-item-count">46</span>
        <span class="site-state-item-name">schlagwörter</span></a>
      </div>
  </nav>
</div>



        </div>
      </div>
    </div>
  </aside>
  <div class="sidebar-dimmer"></div>


    </header>

    
  <div class="back-to-top" role="button">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

<noscript>
  <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>


    <div class="main-inner index posts-expand">

    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh">
    <link itemprop="mainEntityOfPage" href="https://suyuhuan.gitee.io/yuwanzi.io/2020/11/07/2020-11-07-%E4%B8%80%E7%A7%92%E6%90%9E%E6%87%82%E4%BB%80%E4%B9%88%E6%98%AFBFC/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/yuwanzi.io/images/avatar.gif">
      <meta itemprop="name" content="玉丸子">
      <meta itemprop="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="玉丸子 | Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/yuwanzi.io/2020/11/07/2020-11-07-%E4%B8%80%E7%A7%92%E6%90%9E%E6%87%82%E4%BB%80%E4%B9%88%E6%98%AFBFC/" class="post-title-link" itemprop="url">一秒搞懂什么是BFC，告别面试窘境</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Veröffentlicht am</span>
      

      <time title="Erstellt: 2020-11-07 08:55:00 / Geändert am: 08:58:17" itemprop="dateCreated datePublished" datetime="2020-11-07T08:55:00+08:00">2020-11-07</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">in</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%89%8D%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">前端</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8F%E8%A7%88%E5%99%A8/" itemprop="url" rel="index"><span itemprop="name">浏览器</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <blockquote>
<p>面试中经常遇见BFC相关问题，经常问什么情况下会触发BFC，网络博客中也有很多文章提及BFC，至少我看过后，浏览器是如何在正常流下排版元素的，而BFC和IFC在排版元素的时候又是扮演什么角色这部分内容都是没有介绍的。所以搞懂BFC和IFC必须的从浏览器排版前的基础概念讲起。</p>
</blockquote>
<h2 id="什么是标签、元素、盒"><a href="#什么是标签、元素、盒" class="headerlink" title="什么是标签、元素、盒"></a>什么是标签、元素、盒</h2><p>关于标签、元素、盒的的概念，基本上对应着html文本、生成DOM和CSSOM、layout。文本阶段我们把每个闭合或自闭合元素(token)称为标签，通过文本解析(Parse)后生成DOM树一个个的节点称为元素（DOM元素）、之后通过解析后CSSRule(这个过程往往是流式处理的字符流-token流-生成元素及css匹配计算)，匹配到对应的元素节点上，计算排版得到元素所占据的大小位置，也就是所谓的盒。</p>
<p>至少目前，我们应该知道盒是浏览器排版元素基本单位。</p>
<h2 id="浏览器产生盒的过程"><a href="#浏览器产生盒的过程" class="headerlink" title="浏览器产生盒的过程"></a>浏览器产生盒的过程</h2><p>在css标准中有 block-level elements 和 inline-level elements 两个就是我们所说块级元素和行内元素。</p>
<p>在正常流排版中每个块级元素独占一行、逐行排版的， 每个行内元素或文字依次排列，容不下换行依次排列的。</p>
<p>现在，如果站在产生盒的过程，我们把元素的排版大概如下步骤：</p>
<ul>
<li>当遇到块级元素：会产生一个block-level-boxs 块盒， 块盒上下排列。</li>
<li>当遇到行内级元素或者文字： 会产生一个inline-boxs 行盒，当容器容不下元素时，会创建新的行盒依次排列新的行内元素。</li>
</ul>
<p>至少目前盒和元素关系，应该是元素根据自身类型会产生盒，一个块级元素会产生一个块级盒独占一行，而多个行内元素至少会产生一个或多个行盒，多个行盒依次上下排列。一段文本会产生一个或多个行盒。</p>
<ul>
<li>block-level-boxs</li>
<li>inline-boxs</li>
</ul>
<h2 id="关于盒模型"><a href="#关于盒模型" class="headerlink" title="关于盒模型"></a>关于盒模型</h2><p>有了盒，不得不说下一个我们很熟悉的概念盒模型<br>块级盒模型如下如：<br><img src="//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9ba0e9e9782541688b464b0783352306~tplv-k3u1fbpfcp-zoom-1.image"></p>
<p>行模型如下图：<br><img src="//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0e8aba0802574989b9dbe284e468b1b5~tplv-k3u1fbpfcp-zoom-1.image"></p>
<p>至少盒模型包括元素margin、padding、border、content的内容</p>
<p>而行模型涉及行的line-height、top、bottom、middle、text-top、text-bottom、base-line相关概念，也对应css vertical-align相关属性值。</p>
<h2 id="再谈BFC和IFC"><a href="#再谈BFC和IFC" class="headerlink" title="再谈BFC和IFC"></a>再谈BFC和IFC</h2><ul>
<li>我们知道了标签、元素、盒的概念和关系。</li>
<li>我们知道了元素产生盒的过程。</li>
</ul>
<p>接下浏览器选择相应盒模型，计算得出盒所占据位置大小、盒与盒之间的留白等信息。<br>然而不管BFC还是IFC都有一个contex当前所依赖状态，而这个状态根据元素有子元素和父元素的关系， 而元素又会产生盒的关系， 所谓Context应该就是当前排版盒所依赖父级盒block-container。</p>
<p>盒在排版时有其依赖上下文环境父级盒，而其内部或者元素内部又可以产生新的盒，根据盒模型对这些新的盒进行排版，从而产生新的上下文环境就是所谓BFC和IFC了。</p>
<p>而当前BFC和IFC依赖上下文环境， 父级盒则称为block-containers。站在元素角度就是，一个元素内部可以继续按照正常流排版，这个元素所产生的盒为block-containers。</p>
<h2 id="最后是触发BFC还是BFC上文合并，一锤定音了。"><a href="#最后是触发BFC还是BFC上文合并，一锤定音了。" class="headerlink" title="最后是触发BFC还是BFC上文合并，一锤定音了。"></a>最后是触发BFC还是BFC上文合并，一锤定音了。</h2><p>讲清楚之前，我们还是看具体例子来做演示，来看看所谓触发BFC例子，如下图：<br><img src="//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e431bb5d708a449b883e7623c928c469~tplv-k3u1fbpfcp-zoom-1.image"><br>从上图，每个元素的上下间距为20px, 这也侧面margin设计的时候被当做了元素间留白，元素间margin不会被叠加。</p>
<h3 id="上下文合并的栗子"><a href="#上下文合并的栗子" class="headerlink" title="上下文合并的栗子"></a>上下文合并的栗子</h3><p><img src="//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e7e1f6095d434fa1b120181390b646ab~tplv-k3u1fbpfcp-zoom-1.image"><br>去除父级display hidden 属性值之后， 子元素留白空间跟父级别元素留白发生合并，也就是父元素所在BFC和子元素所在BFC发生合并。</p>
<p>我们看文档定义：<br><img src="//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cd070fe7e6364f6ea8b36fcb4d709cc7~tplv-k3u1fbpfcp-zoom-1.image"><br>总结一下文档意思：盒内不会创建新的格式化上下文的情况只有一种，自身为Block conatiner盒 且是为block box的盒，加上元素dispaly 属性为visible时候，盒内不会有新的格式化上下文环境产生。 大白话：块级元素display为visible时候盒父级格式化上下文环境发生合并， 子元素margin和父级元素margin发生折叠（这里我们把文档定义反过来解释，就容易理解多了。）。 </p>
<h3 id="上下文合并与float栗子"><a href="#上下文合并与float栗子" class="headerlink" title="上下文合并与float栗子"></a>上下文合并与float栗子</h3><p><img src="//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa84f9cafba3486d8d1142c766bc77a7~tplv-k3u1fbpfcp-zoom-1.image"></p>
<p>至此我们BFC就讲完了， 总结一下涉及概念</p>
<ul>
<li>标签、元素、盒</li>
<li>盒模型、行模型</li>
<li>BFC、IFC</li>
<li>block-level boxs inline-level boxs block-level element inline-level element</li>
<li>block-box、block-container</li>
</ul>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh">
    <link itemprop="mainEntityOfPage" href="https://suyuhuan.gitee.io/yuwanzi.io/2019/08/24/2019-08-24-Life-of-a-Pixel-2018/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/yuwanzi.io/images/avatar.gif">
      <meta itemprop="name" content="玉丸子">
      <meta itemprop="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="玉丸子 | Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/yuwanzi.io/2019/08/24/2019-08-24-Life-of-a-Pixel-2018/" class="post-title-link" itemprop="url">Life-of-a-Pixel-2018</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Veröffentlicht am</span>

      <time title="Erstellt: 2019-08-24 17:31:36" itemprop="dateCreated datePublished" datetime="2019-08-24T17:31:36+08:00">2019-08-24</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">Bearbeitet am</span>
        <time title="Geändert am: 2020-11-07 08:58:17" itemprop="dateModified" datetime="2020-11-07T08:58:17+08:00">2020-11-07</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">in</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%89%8D%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">前端</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8F%E8%A7%88%E5%99%A8/" itemprop="url" rel="index"><span itemprop="name">浏览器</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="Life-of-a-Pixel-2018"><a href="#Life-of-a-Pixel-2018" class="headerlink" title="Life of a Pixel 2018"></a>Life of a Pixel 2018</h1><p><a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000016335571">译文地址</a></p>
<p><a target="_blank" rel="noopener" href="http://bit.ly/lifeofapixel">原文地址</a></p>
<blockquote>
<p>This talk is about how Chrome turns web content into pixels. The entire process is called “rendering”.<br>We’ll describe what we mean by content, and what we mean by pixels, and then we’ll explain the magic in between.</p>
</blockquote>
<p>这个演讲主要介绍chrome是如何把web content转成像素点的，整个过程被称为“rendering”。<br>我们首先描述什么叫做content，然后再介绍pixels，然后解释其中的魔法。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823213543.png"></p>
<blockquote>
<p>Chrome’s architecture is constantly evolving. This talk connects high-level concepts (which change slowly) to specific classes (which change frequently).<br>The details are based primarily on what is currently shipping in the canary channel (M69), but a few of the biggest future refactorings are mentioned in passing.</p>
</blockquote>
<p>chrome的架构在不断发展。这个演讲将高级概念（变化缓慢）与特定类（经常变化）联系起来。细节基于canary（M69），一些比较大的未来的重构也会被提到。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823213714.png"></p>
<blockquote>
<p>“Content” is the generic term in Chromium for all of the code inside a webpage or the frontend of a web application.<br>It’s made of text, markup (surrounding the text), styles (defining how markup is rendered), and script (which can modify all of the above dynamically).<br>There are other kinds of content, which we won’t cover here.</p>
</blockquote>
<p>“Content”是最通用的Chromium的术语，指的是网页内部或者是网络应用的前端代码。它由text，markup(环绕于文本),style(决定标记如何渲染)，以及script(能够动态地改变上述提到的内容)。同时也有其他的content，但是我们在这里暂时不做介绍。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823213937.png"></p>
<blockquote>
<p>A real webpage is just thousands of lines of HTML, CSS, and JavaScript delivered in plain text over the network.<br>There’s no notion of compilation or packaging as you might find on other kinds of software platforms - the webpage’s source code is the input to the renderer.</p>
</blockquote>
<p>一个真实的网页由成千行的纯文本的HTML，CSS以及JavaScript通过网络提供。你可能在其他软件平台找不到compilation或者packaging的概念，网页的源代码是renderer的输入。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823214046.png"></p>
<blockquote>
<p>Architecturally, the “content” namespace in the Chromium C++ codebase is responsible for everything in the red box.<br>Contrast with tab strip, address bar, navigation buttons, menus, etc. which live outside of “content”.<br>Key to Chrome’s security model: rendering happens in a sandboxed process.</p>
</blockquote>
<p>从架构上说，”content“在Chromium C++ 代码的命名空间负责红框中的所有内容。<br>对比而言，标签条，地址栏，导航按钮，菜单，在”content“之外。<br>Chrome安全模型的关键是，在沙盒进程中进行渲染。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823214159.png"></p>
<blockquote>
<p>At the other end of the pipeline we have to get pixels onto the screen using the graphics libraries provided by the underlying operating system.<br>On most platforms today, that’s a standardized API called “OpenGL”. On Windows there’s an extra translation into DirectX. In the future, we may support newer APIs such as Vulkan.<br>These libraries provide low-level graphics primitives like “textures” and “shaders”, and let you do things like “draw a polygon at these coordinates into a buffer of virtual pixels”. But obviously they don’t understand anything about the web or HTML or CSS.</p>
</blockquote>
<p>在管道的另一边，我们要利用操作系统下提供的图形库把像素点放置在屏幕上。现今，在大多数的平台上，有一个标准API叫做”OpenGL”。在Windows上，有额外到DirectX的转换。在将来，我们会支持新的API（类似Vulkan）。<br>这些库提供了低级图形基元（primitive），类似“纹理（texture）”以及“着色器（shader）”，让你能够做类似“在这些坐标处绘制一个多边形到虚拟像素缓冲区”这样的操作。但是显然他们并不理解任何和HTML和CSS相关的事情。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823214334.png"></p>
<blockquote>
<p>So the goal of rendering can be stated as: turn HTML / CSS / JavaScript into the right OpenGL calls to display the pixels.<br>But keep in mind a second goal as we describe the pipeline: We also want the right intermediate data structures to update the rendering efficiently after it’s produced, and answer queries about it from script or other parts of the system.</p>
</blockquote>
<p>所以渲染的目标可以描述为：将HTML/CSS/JavaScript转化为正确的OpenGL的调用来展示像素。<br>但是要记住，我们描述这个管道的第二个目标是：我们同样想知道正确的中间数据生成后有效地更新，并且能在脚本或者系统的其他部分回答有关它的查询。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823214537.png"></p>
<blockquote>
<p>We break the pipeline into multiple “lifecycle stages”, generating those intermediate outputs.<br>We’ll first describe each stage of a working pipeline, then come back to the notion of efficient updating and introduce some concepts for optimization.</p>
</blockquote>
<p>我们将管道分成多个“生命周期阶段”，产生中间输出。我们首先描述工作流水线的每个阶段，然后回到有效更新的概念，再介绍优化相关的概念。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823214732.png"></p>
<blockquote>
<p>HTML tags impose a semantically meaningful hierarchical structure on the document. For example, a <div> may contain two paragraphs, each with text. So the first step is to parse those tags to build an object model that reflects this structure.</p>
</blockquote>
<p>HTML标签在文档上强加了语义上有意义的层次结构。 例如，&lt;div&gt;可能包含两个段落，每个段落都带有文本。 因此，第一步是解析这些标签以构建反映此结构的对象模型。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823215036.png"></p>
<blockquote>
<p>If you’ve taken computer science classes you may recognize this as a “tree”.</p>
</blockquote>
<p>如果你上过计算机科学的课程，那么你就能认出来它是一棵树。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823215155.png"></p>
<blockquote>
<p>The DOM serves double duty as both the internal representation of the page, and the API exposed to script for querying or modifying the rendering.<br>The JavaScript engine (V8) exposes DOM web APIs as thin wrappers around the real DOM tree through a system called “bindings”.</p>
</blockquote>
<p>DOM具有双重功能，它既能作为页面的内部展示，也能作为查询和更改呈现的脚本的公开API。Javascript引擎(V8)通过一个系统叫做“bindings”把真实的DOM tree封装成一个轻量的wrapper。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823215245.png"></p>
<blockquote>
<p>Having built the DOM tree, the next step is to process the CSS styles.<br>A CSS selector selects a subset of DOM elements that its property declarations should apply to.</p>
</blockquote>
<p>构建了DOM tree以后，下一步是处理CSS styles。一个CSS选择器选择它属性声明了应当应用的DOM元素的子集。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823215411.png"></p>
<blockquote>
<p>Style properties are the knobs by which web authors can influence the rendering of DOM elements.<br>There are hundreds of style properties.</p>
</blockquote>
<p>作者可以通过Style属性影响DOM元素的渲染。style属性有几百个。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823215532.png"></p>
<blockquote>
<p>Furthermore, it’s not trivial to determine which elements a style rule selects.<br>Some elements may be selected by more than one rule, with conflicting declarations for a particular style property.</p>
</blockquote>
<p>此外，确定样式规则选择哪些元素并非易事。<br>某些元素可能由多个规则选择，并且具有特定样式属性的冲突声明。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823215626.png"></p>
<blockquote>
<p>The layout stage runs after the style recalc stage.<br>First, the layout tree is constructed. Then, we walk the layout tree, filling in the geometry data, and processing side effects of the geometry.</p>
</blockquote>
<p>layout阶段在style重计算阶段以后进行。首先，layout tree构建，然后我们遍历这个layout tree，填充几何数据，然后处理几何产生的副作用。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823215720.png"></p>
<blockquote>
<p>Today, layout objects contain both inputs and outputs of the layout stage, without a clean separation between them.<br>For example, the LayoutObject acquires ownership of its element’s ComputedStyle object.<br>A new layout system called LayoutNG is expected to simplify the architecture, and make it easier to build new layout algorithms.</p>
</blockquote>
<p>如今，layout对象包括layout阶段的输入和输出,它们之间的区分没有那么清晰。<br>比如，LayoutObject获取它元素的 ComputedStyle 对象的所有权。一个新的layout系统叫做LayoutNG，被期望用来简化架构，构建新的更简单的layout算法。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823215812.png"></p>
<blockquote>
<p>Now that we understand the geometry of our layout objects, it’s time to paint them.<br>Paint records paint operations into a list of display items.<br>A paint operation might be something like “draw a rectangle at these coordinates, in this color”.<br>There may be multiple display items for each layout object, corresponding to different parts of its visual appearance, like the background, foreground, outline, etc.<br>This is just a recording that can be played back later. We’ll see why that’s useful in a bit.</p>
</blockquote>
<p>现在我们理解了layout object的几何形状，接下来就是绘制它们。<br>paint将paint操作记录为一列display item。一个paint操作类似于“在这些坐标用这个颜色绘制一个矩形”。对于每个layout object也许有多个display item，对应视觉外观的不同部分，比如背景，前景，轮廓等。~~~</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823215913.png"></p>
<blockquote>
<p>It’s important to paint elements in the right order, so that they stack correctly when they overlap.<br>The order can be controlled by style.</p>
</blockquote>
<p>以正确的顺序paint element非常重要，只有这样才能在元素覆盖的时候正常显示。这个顺序能被style所控制。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823220029.png"></p>
<blockquote>
<p>It’s even possible for an element to be partly in front of and partly behind another element.<br>That’s because paint runs in multiple phases, and each paint phase does its own traversal of a subtree.</p>
</blockquote>
<p>一个元素部分在另外一个元素的前面或者后面是可能的。因为paint在不同阶段运行，每次的paint阶段做了自己子树的遍历。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823220106.png"></p>
<blockquote>
<p>The paint operations in the display item list are executed by a process called rasterization.<br>Each cell in the resulting bitmap holds values for four color channels.</p>
</blockquote>
<p>在display item列表上进行paint操作被称为rasterization（光栅化）。位图上的每个像素点存着四个颜色通道的值。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823220156.png"></p>
<blockquote>
<p>The rastered bitmap is stored in memory, typically GPU memory referenced by an OpenGL texture object.<br>The GPU can also run the commands that produce the bitmap (“accelerated rasterization”).<br>Note that these pixels are not yet on the screen!</p>
</blockquote>
<p>光栅化的位图在内存中存储，通常是被OpenGL纹理对象引用的GPU内存。<br>GPU能够运行产生位图的命令（“加速光栅化”）<br>注意到此时像素并不在屏幕上。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823220302.png"></p>
<blockquote>
<p>Rasterization issues OpenGL calls through a library called Skia. Skia provides a layer of abstraction around the hardware, and understands more complex things like paths and Bezier curves.<br>Skia is open-source and maintained by Google. It ships in the Chrome binary but lives in a separate code repository. It’s also used by other products such as the Android OS.<br>Skia’s GPU-accelerated codepath builds its own buffer of drawing operations, which is flushed at the end of the raster task.</p>
</blockquote>
<p>Rasterization通过一个Skia的库产生OpenGL调用，Skia围绕硬件提供了一层抽象，能理解类似路径和贝塞尔曲线等更复杂的事情。<br>Skia开源且由谷歌维护。它附带在chrome二进制文件中，但是属于一个单独的代码库，同时也被其他产品，比如安卓系统使用。<br>Skia的GPU加速代码路径构建了它独立的绘制操作缓冲区，它会在光栅化任务结束的时候刷新。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823220351.png"></p>
<blockquote>
<p>Recall that the renderer process is sandboxed, so it can’t make system calls directly.<br>GL calls issued by Skia are actually proxied into a different process using a “command buffer”.<br>The GPU process receives the command buffer and issues the “real” GL calls through a set of function pointers.<br>Besides escaping the renderer sandbox, isolating GL in the GPU process protects us from unstable or insecure graphics drivers.</p>
</blockquote>
<p>回顾之前说的渲染进程在沙盒中，所以并不能直接进行系统调用。<br>Skia发起的GL调用使用“command buffer”被代理给了另一个不同的进程。<br>GPU进程接受command buffer然后通过函数指针发起“真正”的GL调用。<br>逃离沙盒进程，GPU进程中独立的GL将从不稳定和不安全的图形驱动中保护我们。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823220625.png"></p>
<blockquote>
<p>Those GL function pointers are initialized by dynamic lookup from the system’s shared OpenGL library - or the ANGLE library on Windows.<br>ANGLE is another library built by Google; its job is to translate OpenGL to DirectX, which is Microsoft’s API for accelerated graphics on Windows.<br>There are also OpenGL drivers for Windows, but historically they have not been very high quality.</p>
</blockquote>
<p>这些GL函数指针通过系统的共享OpenGL库（或Windows上的ANGLE库）进行动态查找来初始化。<br>ANGLE是Google建立的另一个库; 它的工作是将OpenGL转换为DirectX，这是微软在Windows上加速图形的API。<br>Windows也有OpenGL驱动程序，但从历史上看，它们的质量并不高。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823220814.png"></p>
<blockquote>
<p>Moving raster to the GPU process will improve performance.<br>It’s also needed to support Vulkan.</p>
</blockquote>
<p>把光栅化移动到GPU进程能够改善性能。它同时需要支持Vulkan</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823220907.png"></p>
<blockquote>
<p>We have now gone all the way from content to pixels in memory.<br>But note that the rendering is not static.<br>Running the full pipeline is expensive</p>
</blockquote>
<p>现在我们已经走完在内存中从content到pixels的全程了。但是渲染本身不是静态的，走完管道全程总是代价昂贵的。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221032.png"></p>
<p>Change is modelled as animation frames.<br>Each frame is a complete rendering of the state of the content at a particular point in time.</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221107.png"></p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221124.png"></p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221139.png"></p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221203.png"></p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221218.png"></p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221237.png"></p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221310.png"></p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221326.png"></p>
<blockquote>
<p>Certain style properties cause a layer to be created for a layout object.<br>If a layout object doesn’t have a layer, it paints into the layer of the nearest ancestor that has one.</p>
</blockquote>
<p>特定的样式属性会为layout对象创建layer。如果一个layout对象本身并没有layer，那么它将被绘制到最近的祖先节点上。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221411.png"></p>
<blockquote>
<p>Building the layer tree is a new lifecycle stage on the main thread. Today, this happens before paint, and each layer is painted separately.</p>
</blockquote>
<p>构建layer tree是主线程上的新生命周期阶段。如今，这发生在paint之前，每层都是单独paint的。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221448.png"></p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221509.png"></p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221535.png"></p>
<blockquote>
<p>After paint is finished, the commit updates a copy of the layer tree on the compositor thread, to match the state of the tree on the main thread.</p>
</blockquote>
<p>paint结束后，commit将更新compositor线程上的layer tree的拷贝，以匹配主线程上tree的状态</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221740.png"></p>
<blockquote>
<p>Recall: raster is the step after paint, which turns paint ops into bitmaps.<br>Layers can be large - rastering the whole layer is expensive, and unnecessary if only part of it is visible.<br>So the compositor thread divides the layer into tiles.<br>Tiles are the unit of raster work. Tiles are rastered with a pool of dedicated raster threads. Tiles are prioritized based on their distance from the viewport.<br>(Not shown: a layer actually has multiple tilings for different resolutions.)</p>
</blockquote>
<p>回想一下：raster在paint之后，它将paint ops转换为位图。<br>layer可能很大 - 整个layer都光栅化的成本很高，如果只有部分可见则不必要全部光栅化。<br>因此，compositor线程将layer划分为tiles。<br>tiles是raster work的基本单位。 专门的raster线程池对tiles进行光栅化。 根据tiles与viewport的距离确定tiles的优先级。<br>（未显示：layer实际上有不同分辨率的多个tilings。）</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823221936.png"></p>
<blockquote>
<p>Once all the tiles are rastered, the compositor thread generates “draw quads”. A quad is like a command to draw a tile in a particular location on the screen, taking into account all the transformations applied by the layer tree. Each quad references the tile’s rastered output in memory (remember, no pixels are on the screen yet).<br>The quads are wrapped up in a compositor frame object which gets submitted to the browser process.</p>
</blockquote>
<p>一旦所有tiles都被光栅化，compositor线程就会生成“draw quads”。 quads类似一个命令，考虑了图层树应用的所有变换在屏幕上的特定位置绘制tile。 每个quads在内存中引用了tile的光栅输出（请记住，屏幕上还没有像素）。<br>quads被包装在一个compositor frame object中，该对象被提交给浏览器进程。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823222111.png"></p>
<blockquote>
<p>The compositor thread has two copies of the tree, so that it can raster tiles from a new commit while drawing the previous commit.</p>
</blockquote>
<p>合成器线程有两个树的副本，因此它可以在绘制上一次commit时，对新的commit进行光栅化tiles。</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823222630.png"></p>
<blockquote>
<p>The browser process runs a component called the display compositor, inside a service called “viz” (short for visuals).<br>The display compositor aggregates compositor frames submitted from all the renderer processes, along with frames from the browser UI outside of the WebContents. Then it issues the GL calls to draw the quad resources, which go to the GPU process just like the GL calls from the raster workers.<br>On most platforms the display compositor’s output is double-buffered, so the quads draw into a backbuffer, and a “swap” command makes it visible.<br>(On OS X we do something a little different with CoreAnimation.)<br>Finally our pixels are on the screen. :)</p>
</blockquote>
<p>浏览器进程在名为“viz”（visual的简称）的service中运行一个名为display compositor的组件。<br>display compositor聚合从所有renderer进程提交的compositor frame，以及来自WebContents外部的browser UI的frame。 然后它发起GL调用来绘制quad资源，这些资源就像来自raster worker的GL调用一样进入GPU进程。<br>在大多数平台上，display compositor的输出是双缓冲的，因此quads绘制到backbuffer，“swap”命令使其可见。<br>（在OS X上，我们使用CoreAnimation做了一些不同的事情。）<br>最后我们的像素在屏幕上。:)</p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823222733.png"></p>
<p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190823222807.png"></p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh">
    <link itemprop="mainEntityOfPage" href="https://suyuhuan.gitee.io/yuwanzi.io/2019/08/10/2019-08-10-How-Blink-Works-%E4%B8%AD%E6%96%87%E8%AF%91%E6%96%87/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/yuwanzi.io/images/avatar.gif">
      <meta itemprop="name" content="玉丸子">
      <meta itemprop="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="玉丸子 | Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/yuwanzi.io/2019/08/10/2019-08-10-How-Blink-Works-%E4%B8%AD%E6%96%87%E8%AF%91%E6%96%87/" class="post-title-link" itemprop="url">How Blink Works 中文译文</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Veröffentlicht am</span>

      <time title="Erstellt: 2019-08-10 14:33:05" itemprop="dateCreated datePublished" datetime="2019-08-10T14:33:05+08:00">2019-08-10</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">Bearbeitet am</span>
        <time title="Geändert am: 2020-11-07 08:58:17" itemprop="dateModified" datetime="2020-11-07T08:58:17+08:00">2020-11-07</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">in</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%89%8D%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">前端</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8F%E8%A7%88%E5%99%A8/" itemprop="url" rel="index"><span itemprop="name">浏览器</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <p><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/52918538">译文地址</a></p>
<blockquote>
<p>Chromium 的工程师们写了两篇技术文章 <a target="_blank" rel="noopener" href="https://docs.google.com/document/d/1aitSOucL0VHZa9Z2vbRJSyAIsAz24kX8LFByQ5xQnUg">How Blink Works</a> 和 <a target="_blank" rel="noopener" href="https://docs.google.com/document/d/1yjzOWrPfFGviEd1nru3yzqxSHETC-zsEBSt9C8SvV-Q/edit#heading=h.gbxb964b3jon">How cc Works</a>，分别介绍了 Chrome 浏览器内核内部的两个重要模块 Blink 和 cc 内部设计和实现的一些细节。对于想要了解 Chrome 内核内部实现的同学，这两篇文章提供了不错的入门指引。在征得作者同意后，我将其翻译成中文，以馈读者。<br>文中部分术语觉得难以翻译成合适的中文的，我会保留原文。对于一些较为复杂的概念会根据自己的理解给出一些额外的解释。如果有我理解有误，没有正确翻译的地方，也请读者留言指出。</p>
</blockquote>
<p>Blink 的开发并不容易。对于新的 Blink 开发者来说，这种不容易体现在内部的许许多多 Blink 特定的概念和编码约定，它们是为了实现一个高性能渲染引擎而引入的。对于有经验的 Blink 开发者来说也是不容易的，因为 Blink 非常庞大，并且对性能，内存占用和安全非常敏感。</p>
<p>本文旨在提供一篇指引，从一万公尺的高空概览 “Blink 是如何工作” 的全貌，我希望这能够帮助 Blink 的开发者快速了解 Blink 的整体架构设计：  </p>
<ul>
<li>本文并不是一篇完整的教程，去解析 Blink 架构的细节和编码规则（这些部分也容易因为发生变化而过时）。相反，本文简明地描述了 Blink 的基础设计，它们短期内不太容易发生变化，如果读者需要了解更多，本文包含了其它资料的指引可供进一步阅读；</li>
<li>本文也不对特定的特性进行解析（比如 ServiceWorkers，editing）。相反，本文描述的是基础特性，它们被其它模块所广泛使用（比如内存管理，V8 APIs）。  </li>
</ul>
<p>有关 Blink 开发的更多一般信息，请参阅 <a target="_blank" rel="noopener" href="https://www.chromium.org/blink">Chromium wiki 页面</a>。</p>
<h2 id="Blink-做些什么"><a href="#Blink-做些什么" class="headerlink" title="Blink 做些什么"></a>Blink 做些什么</h2><p>Blink 是 Web 平台的渲染引擎。粗略地说，Blink 实现了将网页内容绘制到一个浏览器标签内的所有代码：</p>
<ol>
<li>实现了 Web 平台的规范（也就是 HTML 标准），包括 DOM，CSS 和 Web IDL；</li>
<li>内嵌 V8 运行 JavaScript；</li>
<li>通过底层的网络栈请求资源；</li>
<li>构建 DOM 树；</li>
<li>计算样式和排版；</li>
<li>内嵌 Chrome Compositor 用于绘图；  </li>
</ol>
<p>Blink 的使用者比如 Chromium，Android WebView 和 Opera 通过 content public APIs 内嵌 Blink 并调用。<br><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190731133118.png"></p>
<p>从代码结构的角度来看，”Blink” 一般意味着 “//third_party/blink/“ 目录下的代码。从项目的角度来看，”Blink” 一般意味着实现 Web 平台特性的项目。实现这些 Web 特性的代码分布在 “//third_party/blink/“， “//content/renderer/“，”//content/browser/“ 和其它地方。</p>
<blockquote>
<p>[译注] 1. 关于 Embedder 的概念，可以参考我的一篇旧文 - <a target="_blank" rel="noopener" href="https://blog.csdn.net/rogeryi/article/details/48179851">理解 Embedder，理解 Chromium 的系统层次结构</a>。 2. “Blink 实现了将网页内容绘制到一个浏览器标签内的所有代码” 这句话我的理解应该是指广义的 “Blink”，并不仅仅指 “//third_party/blink/“。</p>
</blockquote>
<h2 id="进程-线程架构"><a href="#进程-线程架构" class="headerlink" title="进程/线程架构"></a>进程/线程架构</h2><h3 id="进程"><a href="#进程" class="headerlink" title="进程"></a>进程</h3><p>Chromium 使用<a target="_blank" rel="noopener" href="https://www.chromium.org/developers/design-documents/multi-process-architecture">多进程的架构</a>。 Chromium 拥有一个 browser 进程和 N 个沙盒 renderer 进程。Blink 运行在 renderer 进程。  </p>
<p>有多少个 renderer 进程会被创建？为了安全的原因，对跨站的 documents 进行内存地址空间隔离是非常重要的（这被称为 (<a target="_blank" rel="noopener" href="https://www.chromium.org/Home/chromium-security/site-isolation">Site Isolation</a>）。理想的情况，每个站点都应该分配一个独立的 renderer 进程，但是实际上，当用户打开太多的标签页，或者设备没有足够的内存，就很难限制每个 renderer 进程都只对应一个站点。所以有时一个 renderer 进程会被多个来自不同站点的 iframes 或者标签页所共享。这也意味着一个标签页下的 iframes 可能位于不同的 renderer 进程，或者不同标签页的 iframes 位于同一个 renderer 进程。renderer 进程，iframes，标签页三者之间不是完全 1:1 的映射关系。</p>
<p>因为 renderer 进程运行在沙盒环境下，Blink 需要请求 browser 进程去处理系统调用（比如文件访问，播放音频等），还有访问用户的账号数据（比如 cookie，密码等）。browser-renderer 进程间通讯是由 <a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md">Mojo</a> 来实现的。(过去是使用 <a target="_blank" rel="noopener" href="https://www.chromium.org/developers/design-documents/inter-process-communication">Chromium IPC</a>，现在还有很多代码仍旧继续使用。不过 Chromium IPC 已经逐步放弃，实际上它的底层也换成了用 Mojo 实现) 对 Chromium 来说，服务化还在继续进行，browser 进程会被抽象成一组服务的集合。从 Blink 的角度来看，Blink 可以通过 Mojo 跟其它服务和 browser 进程进行交互。<br><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190731205610.png"></p>
<p>如果你希望了解更多：</p>
<ol>
<li>多进程架构</li>
<li>Blink 的 Mojo 编程：platform/mojo/MojoProgrammingInBlink.md</li>
</ol>
<h3 id="线程"><a href="#线程" class="headerlink" title="线程"></a>线程</h3><p>在 renderer 线程会创建多少个线程？</p>
<p>Blink 会拥有一个主线程，N 个 worker 线程和一些内部线程。</p>
<p>几乎所有的主要活动都发生在主线程。所有的 JavaScript 调用（除了运行在 workers 线程的 JS 代码），DOM，CSS，样式和排版计算都运行在主线程。Blink 经过高度优化来最大化主线程的性能，它的架构被认为主要是单线程的。</p>
<p>Blink 可能会创建若干 worker 线程来运行 <a target="_blank" rel="noopener" href="https://html.spec.whatwg.org/multipage/workers.html#workers">Web Workers</a>，<a target="_blank" rel="noopener" href="https://w3c.github.io/ServiceWorker/">Service Worker</a>，和 <a target="_blank" rel="noopener" href="https://www.w3.org/TR/worklets-1/">Worklets</a>。</p>
<p>Blink 和 V8 还可能会创建一些内部线程来处理 webaudio，database，GC 等等。</p>
<p>对跨线程通讯，你需要使用 PostTask APIs 来发送消息。我们并不鼓励使用共享内存编程，除了少数地方因为性能的原因才需要使用。这也是你在 Blink 的代码里面并没有看到太多需要使用 Mutex 加锁的原因。<br><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190731211342.png"></p>
<p>如果你希望了解更多：</p>
<ol>
<li>Blink 的线程编程：platform/wtf/ThreadProgrammingInBlink.md</li>
<li>Workers: <a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/core/workers/README.md">core/workers/README.md</a></li>
</ol>
<h3 id="Blink-的初始化和终结"><a href="#Blink-的初始化和终结" class="headerlink" title="Blink 的初始化和终结"></a>Blink 的初始化和终结</h3><p>Blink 的初始化位于 <a target="_blank" rel="noopener" href="https://cs.chromium.org/chromium/src/third_party/blink/renderer/controller/blink_initializer.cc?sq=package:chromium&dr=C&g=0&l=86">BlinkInitializer::Initialize()</a>，在执行任何 Blink 代码之前必须先调用该方法。</p>
<p>另一方面来说，Blink 并没有提供终结的方法，也就是说，我们会强制退出 renderer 进程，也不做任何清理。一个原因是因为性能。另外一个原因是，通常非常困难以优雅有序的方式去清理 renderer 进程的所有内容（并且也不值得去做）。</p>
<h2 id="目录结构"><a href="#目录结构" class="headerlink" title="目录结构"></a>目录结构</h2><h3 id="Content-public-APIs-和-Blink-public-APIs"><a href="#Content-public-APIs-和-Blink-public-APIs" class="headerlink" title="Content public APIs 和 Blink public APIs"></a>Content public APIs 和 Blink public APIs</h3><p><a target="_blank" rel="noopener" href="https://cs.chromium.org/chromium/src/content/public/">Content public APIs</a> 是 embedders 使用来嵌入 Blink 渲染引擎的 API 层。Content public APIs 需要小心地进行维护，因为它们会被暴露给 embedders。</p>
<p><a target="_blank" rel="noopener" href="https://cs.chromium.org/chromium/src/third_party/blink/public/?q=blink/public&sq=package:chromium&dr">Blink public APIs</a> 是将 //third_party/blink/ 的功能暴露给 Chromium 的 API 层。这个 API 层是从 WebKit 时代继承过来的历史产物。在 WebKit 时代，Chromium 和 Safari 共享同样的 WebKit 实现，所以我们需要一个 API 层来暴露 WebKit 内部的功能给 Chromium 和 Safari 使用。现在 Chromium 是 //third_party/blink/ 的唯一 embedder，所以实际上我们已经不再需要一个额外的 API 层。我们正在积极地减少 Blink public APIs 的数量，通过将更多的 web 平台相关的代码从 Chromium 移到 Blink 内部（这个项目被成为 Onion Soup）。<br><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190801063159.png"></p>
<h3 id="目录结构和依赖性"><a href="#目录结构和依赖性" class="headerlink" title="目录结构和依赖性"></a>目录结构和依赖性</h3><p>//third_party/blink/ 的子目录如下。<a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/README.md">这篇文档</a>提供了更多的信息：</p>
<ul>
<li>platform/ - Blink 的低阶功能集合，从庞大的 core/ 中分解出来，包括 geometry 和 graphics 等相关的功能。</li>
<li>core/ 和 modules/ - 在规范定义的所有 web 平台特性的实现。core/ 实现了跟 DOM 紧密相关的特性。modules/ 实现的特性相对来说会更自包含。比如 webaudio，indexeddb。</li>
<li>bindings/core 和 bindings/modules/ - 概念上来说 bindings/core 属于 core/ 的一部分，bindings/modules/ 属于 modules/ 的一部分。放置于独立的目录是因为这部分代码跟 V8 紧密相关。</li>
<li>controller/ 一些使用 core/ 和 modules/ 的高级库。比如，devtools 的前端。</li>
</ul>
<p>依赖关系的方向如下：</p>
<p>Chromium =&gt; controller/ =&gt; modules/ and bindings/modules/ =&gt; core/ and bindings/core/ =&gt; platform/ =&gt; low-level primitives such as //base, //v8 and //cc</p>
<p>Blink 很小心地维护哪些低阶基础模块可以被 //third_party/blink/ 所使用。</p>
<p>如果你希望了解更多：</p>
<p>目录结果和依赖性：<a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/README.md">blink/renderer/README.md</a></p>
<blockquote>
<p>[译注] Blink 需要很小心地避免过多的外部依赖，维持更高程度的自包含性，但是像 //base 和 //cc 这样的基础模块都不能直接使用的话，也会导致非常复杂的桥接层，重复实现和多余的类型转换，也妨碍了 Blink 后续的演进。早期 Blink 的确是不能使用 //base 和 //cc。</p>
</blockquote>
<h3 id="WTF"><a href="#WTF" class="headerlink" title="WTF"></a>WTF</h3><p>WTF 是 “Blink 版本的 base” 库，位于 platform/wtf/. 我们试图尽可能统一 Chromium 和 Blink 所使用的基础编码原语，所以 WTF 应该规模很小。这个库仍然被需要是因为仍有一些类型，容器和宏是针对 Blink 特定的用途所优化的，另外还有 Olipan（Blink GC）。如果一个类型在 WTF 中存在，Blink 需要优先使用 WTF 的版本而不是 //base 或者 std 库。最常见的例子是 vectors，hashsets，hashmaps 和 strings。Blink 应该使用 WTF::Vector，WTF::HashSet，WTF::HashMap，WTF::String 和 WTF::AtomicString，而不是 std::vector，std::set，std::map 和 std::string。</p>
<p>如果你希望了解更多：</p>
<p>如何使用WTF：<a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/platform/wtf/README.md">platform/wtf/README.md</a></p>
<h2 id="内存管理"><a href="#内存管理" class="headerlink" title="内存管理"></a>内存管理</h2><p>在 Blink 关注的范畴内，你需要了解三类不同的内存分配器：</p>
<ul>
<li>PartitionAlloc</li>
<li>Olipan（也就是所谓的 Blink GC）</li>
<li>malloc（不鼓励使用）</li>
</ul>
<p>使用 USING_FAST_MALLOC() 可以将对象分配到 PartitionAlloc 的堆上：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">SomeObject</span> &#123;</span></span><br><span class="line">        USING_FAST_MALLOC(SomeObject);</span><br><span class="line">        <span class="function"><span class="keyword">static</span> <span class="built_in">std</span>::<span class="built_in">unique_ptr</span>&lt;SomeObject&gt; <span class="title">Create</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">std</span>::make_unique&lt;SomeObject&gt;();  <span class="comment">// Allocated on PartitionAlloc&#x27;s heap.</span></span><br><span class="line">   &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>使用 PartitionAlloc 分配的对象生命周期应该使用 scoped_refptr&lt;&gt; 或者 std::unique_ptr&lt;&gt; 来管理。我们强烈地不鼓励手动管理对象的生命周期。手动调用 delete 在 Blink 里面是被禁止的。</p>
<p>使用 GarbageCollected 可以在 Olipan 堆上分配一个对象：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SomeObject</span> :</span> <span class="keyword">public</span> GarbageCollected&lt;SomeObject&gt; &#123;</span><br><span class="line">    <span class="function"><span class="keyword">static</span> SomeObject* <span class="title">Create</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> SomeObject;  <span class="comment">// Allocated on Oilpan&#x27;s heap.</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>Oilpan 分配的对象生命周期是由垃圾收集机制来管理的。你必须使用特殊的指针来持有 Oilpan 堆上的对象（比如，Member&lt;&gt;, Persistent&lt;&gt;）。需要了解 Oilpan 的编码限制可以查阅这份 API参考。其中最重要的限制是不允许在一个 Olipan 对象的析构函数里面访问其它 Olipan 对象，因为析构的顺序是无法保证的。</p>
<p>如果你不使用 USING_FAST_MALLOC() 或者 GarbageCollected，对象就会直接分配在系统的 malloc 堆上。我们强烈地不建议在 Blink 里面这样做。所有的 Blink 对象都应该使用 PartitionAlloc 或者 Oilpan 进行分配，规则如下：</p>
<ul>
<li>默认使用 Oilpan。</li>
<li>仅当满足以下条件时可以使用 PartitionAlloc 1) 对象的生命周期非常清晰，使用 std::unique_ptr&lt;&gt; 就足够了，2) 使用 Olipan 分配对象增加了大量的复杂度，或者 3) 使用 Oilpan 分配对象给垃圾收集运行时带来太大不必要的压力。</li>
</ul>
<p>无论是使用 PartitionAlloc 还是 Oilpan，你都需要非常小心避免造成悬挂指针（注：强烈不建议使用 raw pointers）或者内存泄露。</p>
<p>如果你希望了解更多：</p>
<ul>
<li>如何使用 PartitionAlloc: platform/wtf/allocator/Allocator.md</li>
<li>如何使用 Oilpan: platform/heap/BlinkGCAPIReference.md</li>
<li>Oilpan GC 设计: platform/heap/BlinkGCDesign.md</li>
</ul>
<h2 id="任务调度"><a href="#任务调度" class="headerlink" title="任务调度"></a>任务调度</h2><p>为了改进渲染引擎的响应性，Blink 的任务应该尽可能异步执行。同步 IPC/Mojo 或者其它可能花费几毫秒的操作都应该尽量避免（虽然有些确实无法避免，比如运行用户的 JavaScript）。</p>
<p>所有在 renderer 进程执行的任务都需要通过 Blink Scheduler 提交，并且需使用合适的任务类型作为参数，比如：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Post a task to frame&#x27;s scheduler with a task type of kNetworking</span></span><br><span class="line">frame-&gt;GetTaskRunner(TaskType::kNetworking)-&gt;PostTask(..., WTF::Bind(&amp;Function));</span><br></pre></td></tr></table></figure>
<p>Blink Scheduler 维护了多个任务队列，并巧妙地确定任务的优先级，以最大化用户可感知的性能。 指定正确的任务类型使得 Blink Scheduler 可以正确且巧妙地调度任务是非常重要。</p>
<p>如果你希望了解更多：</p>
<ul>
<li>如何提交任务：platform/scheduler/PostTask.md</li>
</ul>
<h2 id="Page-Frame-Document-DOMWindow-和其它"><a href="#Page-Frame-Document-DOMWindow-和其它" class="headerlink" title="Page, Frame, Document, DOMWindow 和其它"></a>Page, Frame, Document, DOMWindow 和其它</h2><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><p>Page, Frame, Document, ExecutionContext 和 DOMWindow 这些 Blink 里面的的重要对象，它们的概念如下：</p>
<ul>
<li>一个 Page 对应着一个标签页（如果 OOPIF 没有开启）。每个 renderer 进程可以拥有多个标签页。</li>
<li>一个 Frame 对应着网页里面的 frame（主 frame 或者 iframe）。每个 Page 都可能包含一个或者多个 Frame，构成树状的结构。</li>
<li>一个 DOMWindow 对应 JavaScript 里面的 window 对象。每个 Frame 拥有一个 DOMWindow。</li>
<li>一个 Document 对应 JavaScript 里面的 window.document 对象。每个 Frame 拥有一个 Document。</li>
<li>一个 ExecutionContext 是 Document（主线程）或者 WorkerGlobalScope（worker 线程）的抽象。</li>
</ul>
<p>Renderer 进程 : Page = 1 : N Page : Frame = 1 : M Frame : DOMWindow : Document (或者 ExecutionContext) 无论何时都是 1 : 1 : 1 的关系，但是映射的对象可能会发生变化。比如，考虑如下的代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">iframe.contentWindow.location.href = <span class="string">&quot;https://example.com&quot;</span>;</span><br></pre></td></tr></table></figure>
<p>上面的例子里面，Blink 会为 <a target="_blank" rel="noopener" href="https://example.com/">https://example.com</a> 创建一个新的 Window 和新的 Document 对象。但是它们仍然对应原来的 Frame 对象。</p>
<p>（注：更精确地说，仍有一些特定的情况，我们会创建新的 Document 对象，但是仍旧重用原来的 Window 和 Frame 对象。另外还有一些更<a target="_blank" rel="noopener" href="https://docs.google.com/presentation/d/1pHjF3TNCX--j0ss3SK09pXlVOFK0Cdq6HkMcOzcov1o/edit#slide=id.g4983c55b2d55fcc7_42">复杂的情况</a>）</p>
<p>如果你希望了解更多：</p>
<ul>
<li>core/frame/FrameLifecycle.md</li>
</ul>
<h3 id="进程外-iframe（OOPIF）"><a href="#进程外-iframe（OOPIF）" class="headerlink" title="进程外 iframe（OOPIF）"></a>进程外 iframe（OOPIF）</h3><p><a target="_blank" rel="noopener" href="https://www.chromium.org/developers/design-documents/site-isolation">Site Isolation</a> 机制可以进一步保护网页的安全，但是也使得事件变得更复杂。Site Isolation 的基本思路是一个 renderer 进程只对应一个站点。（一个站点是由网页的注册域名和它的 URL scheme 组合定义的。例如，<a target="_blank" rel="noopener" href="https://mail.example.com/">https://mail.example.com</a> 和 <a target="_blank" rel="noopener" href="https://chat.example.com/">https://chat.example.com</a> 被认为是同一个站点，但是 <a target="_blank" rel="noopener" href="https://noodles.com/">https://noodles.com</a> 和 <a target="_blank" rel="noopener" href="https://pumpkins.com/">https://pumpkins.com</a> 就不是）如果一个 Page 包括一个跨站的 iframe，那么这个 Page 应该被两个 renderer 进程所托管。考虑如下页面：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- https://example.com --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">iframe</span> <span class="attr">src</span>=<span class="string">&quot;https://example.com&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">iframe</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>主 frame 和 &lt;\iframe&gt; 可能从属于不同的 renderer 进程。一个从属于该 renderer 进程的 frame 对象由 LocalFrame 表征，而不从属于该 renderer 进程的 frame 对象用 RemoteFrame 表征。</p>
<p>上面的例子，如果从主 frame 的角度看，主 frame 是 LocalFrame 而 &lt;\iframe&gt; 是 RemoteFrame。从 &lt;\iframe&gt; 的角度看，主 frame 是 RemoteFrame 而 &lt;\iframe&gt; 是 LocalFrame。</p>
<p>LocalFrame 和 RemoteFrame 之间的通讯通过 browser 进程来处理（它们可能存在于不同的 renderer 进程）。</p>
<p>如果你希望了解更多：</p>
<ul>
<li>设计文档：<a target="_blank" rel="noopener" href="https://www.chromium.org/developers/design-documents/site-isolation">Site isolation 设计文档</a></li>
<li>如何编写符合 Site isolation 的代码：core/frame/SiteIsolation.md</li>
</ul>
<h3 id="Detached-Frame-Document"><a href="#Detached-Frame-Document" class="headerlink" title="Detached Frame/Document"></a>Detached Frame/Document</h3><p>Frame/Document 对象可能会处于分离状态。考虑下面的情况：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">doc = iframe.contentDocument;</span><br><span class="line">iframe.remove();  <span class="comment">// The iframe is detached from the DOM tree.</span></span><br><span class="line">doc.createElement(<span class="string">&quot;div&quot;</span>);  <span class="comment">// But you still can run scripts on the detached frame.</span></span><br></pre></td></tr></table></figure>
<p>一个吊诡的事实是你仍然可以在分离的 frame 上面运行脚本和 DOM 操作。但是由于 frame 已经处于分离状态，大部分的 DOM 操作都会失败并抛出错误。不幸的是，已分离的 frame 的行为在规范上并没有明确定义，不同浏览器的实现也有差异。基本上可以期望的是，脚本仍然可以正常运行，但是除了少数适当的例外，大部分 DOM 操作都会失败，例如：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">someDOMOperation</span><span class="params">(...)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!script_state_-&gt;ContextIsValid()) &#123; <span class="comment">// The frame is already detached</span></span><br><span class="line">    …;  <span class="comment">// Set an exception etc</span></span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>通常这意味着，当 frame 被分离的时候，Blink 需要进行一系列的清理操作。你可以通过继承 ContextLifecycleObserver 来监听该事件，如下所示：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SomeObject</span> :</span> <span class="keyword">public</span> GarbageCollected&lt;SomeObject&gt;, <span class="keyword">public</span> ContextLifecycleObserver &#123;</span><br><span class="line">  <span class="function"><span class="keyword">void</span> <span class="title">ContextDestroyed</span><span class="params">()</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">    <span class="comment">// Do clean-up operations here.</span></span><br><span class="line">  &#125;</span><br><span class="line">  ~SomeObject() &#123;</span><br><span class="line">    <span class="comment">// It&#x27;s not a good idea to do clean-up operations here because it&#x27;s too late to do them. Also a destructor is not allowed to touch any other objects on Oilpan&#x27;s heap.</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="Web-IDL-绑定"><a href="#Web-IDL-绑定" class="headerlink" title="Web IDL 绑定"></a>Web IDL 绑定</h2><p>当 JavaScript 访问 node.firstChild 时，将调用 node.h 中的 Node::firstChild()。 它是如何工作的？ 让我们来看看 node.firstChild 的实现。</p>
<p>首先，你需要根据规范定义 IDL 文件：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// node.idl</span></span><br><span class="line">interface Node : EventTarget &#123;</span><br><span class="line">  [...] readonly attribute Node? firstChild;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>Web IDL 的语法在 <a target="_blank" rel="noopener" href="https://heycam.github.io/webidl/">Web IDL 规范</a>中定义。 […] 称为 IDL 扩展属性。一些 IDL 扩展属性在 Web IDL 规范中定义，而其他的则是 <a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/IDLExtendedAttributes.md">Blink 特定的 IDL 扩展属性</a>。 除了 Blink 特定的 IDL 扩展属性外，IDL 文件应以符合规范的方式编写（即，只需从规范中复制和粘贴）。</p>
<blockquote>
<p>[译注] [Affects=Nothing, PerWorldBindings] readonly attribute Node? firstChild; 像上面的实际例子，Affects=Nothing 和 PerWorldBindings 就是扩展属性，有些是在规范内的，有些是 Blink 自己特有的。</p>
</blockquote>
<p>其次，您需要为 Node 定义 C++ 类并为 firstChild 实现 C++ getter：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventTarget</span> :</span> <span class="keyword">public</span> ScriptWrappable &#123;  <span class="comment">// All classes exposed to JavaScript must inherit from ScriptWrappable.</span></span><br><span class="line">    ...;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Node</span> :</span> <span class="keyword">public</span> EventTarget &#123;</span><br><span class="line">    DEFINE_WRAPPERTYPEINFO();  <span class="comment">// All classes that have IDL files must have this macro.</span></span><br><span class="line">    <span class="function">Node* <span class="title">firstChild</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> first_child_; &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>一般情况而言，就是像上面这样。构建 node.idl 时，IDL 编译器会自动为 Node 接口和 Node.firstChild 生成 Blink-V8 绑定。自动生成的绑定位于 //src/out/{Debug，Release}/gen/third_party/blink/renderer/bindings/core/v8/ v8_node.h。当 JavaScript 调用 node.firstChild 时，V8 在 v8_node.h 中调用 V8Node::firstChildAttributeGetterCallback()，然后再调用你在上面定义的 Node::firstChild()。</p>
<p>如果你希望解更多：</p>
<ul>
<li>如何添加 Web IDL 绑定：<a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/IDLCompiler.md">bindings/IDLCompiler.md</a></li>
<li>如何使用 IDL 扩展属性：<a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/IDLExtendedAttributes.md">bindings/IDLExtendedAttributes.md</a></li>
<li>规范：<a target="_blank" rel="noopener" href="https://heycam.github.io/webidl/">Web IDL 规范</a></li>
</ul>
<h2 id="V8-和-Blink"><a href="#V8-和-Blink" class="headerlink" title="V8 和 Blink"></a>V8 和 Blink</h2><h3 id="Isolate-Context-World"><a href="#Isolate-Context-World" class="headerlink" title="Isolate, Context, World"></a>Isolate, Context, World</h3><p>当你编写涉及 V8 API 的代码时，了解 Isolate，Context 和 World 的概念非常重要。它们分别由代码库中的 v8::Isolate，v8::Context 和 DOMWrapperWorld 表示。</p>
<p>Isolate 对应于物理线程。Isolate:物理线程的比例在 Blink 中是 1:1。主线程有自己的 Isolate。Worker 线程也有自己的 Isolate。</p>
<p>Context 对应于全局对象（在 Frame 的情况下，它是 Frame 的 window 对象）。由于每个 Frame 有自己的 window 对象，因此在 renderer 进程中会有多个 Contexts。当你调用 V8 API 时，你必须确保你处于正确的 Context 中。否则，v8::Isolate::GetCurrentContext() 将返回错误的 Context，在最坏的情况下，它将最终导致对象泄漏并导致安全问题。</p>
<p>World 是用于支持 Chrome 扩展程序中的 content 脚本的概念。World 并不对应 Web 标准中的任何内容。Content 脚本希望与网页共享 DOM，但出于安全原因，Content 脚本的 JavaScript 对象必须与网页的 JavaScript 堆进行隔离。 （并且一个 content 脚本的 JavaScript 堆也必须与另一个 content 脚本的 JavaScript 堆隔离。）为了实现隔离，主线程为网页创建一个 main world，为每个 Content 脚本创建一个 isolated world。Main world 和 isolated world 可以访问相同的 C++ DOM 对象，但它们的 JavaScript 对象是隔离的。我们通过为一个 C++ DOM 对象创建多个 V8 wrappers 来实现这种隔离。即一个 C++ DOM 对象在每个 world 都有一个对应的 V8 wrapper。<br><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190801133847.png"></p>
<p>Context，World 和 Frame 之间有什么关系？</p>
<p>想象一下主线程线上有 N 个 Worlds（一个 main world +（N - 1）个 isolated world）。 然后一个 Frame 应该有 N 个 window 对象，一个 window 对象被一个对应的 world 所使用。Context 是对应于 window 对象的概念。 这意味着当我们有 M 个 Frames 和 N 个 Worlds 时，我们有 M * N Contexts（但是 Contexts 是延迟创建的）。</p>
<p>对于 worker 来说，只有一个 World 和一个全局对象。因此只有一个 Context。</p>
<p>再次强调，当你使用 V8 API 时，应该非常小心保证使用正确的 context。 否则，你最终可能会在 isolated world 之间泄漏 JavaScript 对象并导致安全灾难（例如，<a target="_blank" rel="noopener" href="http://a.com/">http://A.com</a> 的扩展可以操纵来自 <a target="_blank" rel="noopener" href="http://b.com/">http://B.com</a> 的扩展）。</p>
<p>如果你希望了解更多：</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md">bindings/core/v8/V8BindingDesign.md</a></li>
</ul>
<h3 id="V8-APIs"><a href="#V8-APIs" class="headerlink" title="V8 APIs"></a>V8 APIs</h3><p>V8 API 大部分都是在 //v8/include/v8.h 中定义。由于 V8 API 处于较低层次并且难以正确使用，因此 platform/bindings/ 提供了一堆对 V8 API 进行包装的辅助类。一般而言应该尽可能优先使用辅助类。 如果你的代码必须大量使用 V8 API，那么这些代码文件应该放在 bindings/{core，modules} 中。</p>
<p>V8 使用句柄指向 V8 对象。最常见的句柄是 v8::Local&lt;&gt;，用于指向堆栈中的 V8 对象。必须先在堆栈上分配 v8::HandleScope 后，再使用 v8::Local&lt;&gt;。另外不应在堆栈外使用 v8::Local&lt;&gt;：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">function</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  v8::HandleScope scope;</span><br><span class="line">  v8::Local&lt;v8::Object&gt; object = ...;  <span class="comment">// This is correct.</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SomeObject</span> :</span> <span class="keyword">public</span> GarbageCollected&lt;SomeObject&gt; &#123;</span><br><span class="line">  v8::Local&lt;v8::Object&gt; object_;  <span class="comment">// This is wrong.</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>如果是在堆栈外部指向 V8 对象，则需要使用 wrapper tracing。但是，必须非常小心避免创建循环引用。一般而言，V8 API 的确难以使用。如果你不太确定应该怎么做，请咨询 blink-review-bindings@。</p>
<p>如果你希望了解更多：</p>
<ul>
<li>如何使用 V8 API 和辅助类：platform/bindings/HowToUseV8FromBlink.md</li>
</ul>
<h3 id="V8-wrappers"><a href="#V8-wrappers" class="headerlink" title="V8 wrappers"></a>V8 wrappers</h3><p>每个 C++ DOM 对象（例如，Node）都有其对应的 V8 wrapper。准确地说，每个 C++ DOM对象在每个 world 都有对应的 V8 wrapper。</p>
<p>V8 wrappers 对它们对应的 C++ DOM 对象持有强引用。反之，C++ DOM 对象对 V8 wrappers 只持有弱引用。 所以如果希望 V8 wrappers 保持存活一段时间，则必须显式地进行声明。 否则，V8 wrappers 将可能被过早回收，而 V8 wrappers 上的 JS 属性将会丢失…</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">div = <span class="built_in">document</span>.getElementbyId(<span class="string">&quot;div&quot;</span>);</span><br><span class="line">child = div.firstChild;</span><br><span class="line">child.foo = <span class="string">&quot;bar&quot;</span>;</span><br><span class="line">child = <span class="literal">null</span>;</span><br><span class="line">gc();  <span class="comment">// If we don&#x27;t do anything, the V8 wrapper of |firstChild| is collected by the GC.</span></span><br><span class="line">assert(div.firstChild.foo === <span class="string">&quot;bar&quot;</span>);  <span class="comment">//...and this will fail.</span></span><br></pre></td></tr></table></figure>
<p>如果我们不做任何事情，child 就会被 GC 回收，因此 child.foo 将丢失。为了使 div.firstChild 的 V8 wrapper 保持存活，我们需要增加一种机制，“只要 div 所属的 DOM 树从 V8 是可达的，则让 div.firstChild 的 V8 wrapper 处于存活状态”。</p>
<p>有两种方法可以保持 V8 wrappers 存活：<a target="_blank" rel="noopener" href="https://cs.chromium.org/chromium/src/third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h?q=activescriptwrappable&sq=package:chromium&dr=CSs&l=16">ActiveScriptWrappable</a> 和 <a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/platform/bindings/TraceWrapperReference.md">wrapper tracing</a>。</p>
<p>如果你希望了解更多：</p>
<ul>
<li>如何管理 V8 wrappers 的生命周期：bindings/core/v8/V8Wrapper.md</li>
<li>如何使用 wrapper tracing：<a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/platform/bindings/TraceWrapperReference.md">platform/bindings/TraceWrapperReference.md</a></li>
</ul>
<h2 id="渲染流水线"><a href="#渲染流水线" class="headerlink" title="渲染流水线"></a>渲染流水线</h2><p>从传送给 Blink 的 HTML 文档到屏幕上显示的像素是一个十分漫长的旅程。渲染流水线架构的示意图如下：<br><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190806073352.png"></p>
<p>这一篇<a target="_blank" rel="noopener" href="https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit#slide=id.g25ae9c179b_0_96">非常棒的文章</a>描述了上面渲染流水线的每一个步骤。我想恐怕我无法写的比这篇文章更好了 :-)</p>
<p>如果你希望了解更多:</p>
<ul>
<li>概览: <a target="_blank" rel="noopener" href="https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit#slide=id.g25ae9c179b_0_75">Life of a pixel</a></li>
<li>DOM: <a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/core/dom/README.md">core/dom/README.md</a></li>
<li>Style: <a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/core/css/README.md">core/css/README.md</a></li>
<li>Layout: <a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/core/layout/README.md">core/layout/README.md</a></li>
<li>Paint: <a target="_blank" rel="noopener" href="https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/core/paint/README.md">core/paint/README.md</a></li>
<li>Compositor thread: <a target="_blank" rel="noopener" href="https://www.chromium.org/developers/design-documents/chromium-graphics">Chromium graphics</a></li>
</ul>
<h2 id="更多问题？"><a href="#更多问题？" class="headerlink" title="更多问题？"></a>更多问题？</h2><p>如果你有更多问题，可以发邮件到 <a href="mailto:&#98;&#108;&#105;&#110;&#107;&#x2d;&#100;&#101;&#x76;&#64;&#x63;&#x68;&#114;&#111;&#x6d;&#x69;&#117;&#x6d;&#x2e;&#x6f;&#x72;&#x67;">&#98;&#108;&#105;&#110;&#107;&#x2d;&#100;&#101;&#x76;&#64;&#x63;&#x68;&#114;&#111;&#x6d;&#x69;&#117;&#x6d;&#x2e;&#x6f;&#x72;&#x67;</a>（一般性的问题），或者 <a href="mailto:&#112;&#x6c;&#x61;&#116;&#x66;&#111;&#114;&#x6d;&#45;&#x61;&#114;&#99;&#104;&#105;&#x74;&#x65;&#99;&#x74;&#x75;&#x72;&#101;&#x2d;&#x64;&#101;&#x76;&#64;&#99;&#x68;&#114;&#x6f;&#x6d;&#x69;&#117;&#x6d;&#46;&#x6f;&#114;&#103;">&#112;&#x6c;&#x61;&#116;&#x66;&#111;&#114;&#x6d;&#45;&#x61;&#114;&#99;&#104;&#105;&#x74;&#x65;&#99;&#x74;&#x75;&#x72;&#101;&#x2d;&#x64;&#101;&#x76;&#64;&#99;&#x68;&#114;&#x6f;&#x6d;&#x69;&#117;&#x6d;&#46;&#x6f;&#114;&#103;</a>（架构相关的问题）。我们总是乐于提供帮助！:D</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh">
    <link itemprop="mainEntityOfPage" href="https://suyuhuan.gitee.io/yuwanzi.io/2019/07/27/2019-07-27-How-Chromium-Displays-Web-Pages/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/yuwanzi.io/images/avatar.gif">
      <meta itemprop="name" content="玉丸子">
      <meta itemprop="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="玉丸子 | Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/yuwanzi.io/2019/07/27/2019-07-27-How-Chromium-Displays-Web-Pages/" class="post-title-link" itemprop="url">How-Chromium-Displays-Web-Pages</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Veröffentlicht am</span>

      <time title="Erstellt: 2019-07-27 08:25:11" itemprop="dateCreated datePublished" datetime="2019-07-27T08:25:11+08:00">2019-07-27</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">Bearbeitet am</span>
        <time title="Geändert am: 2020-11-07 08:58:17" itemprop="dateModified" datetime="2020-11-07T08:58:17+08:00">2020-11-07</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">in</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%89%8D%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">前端</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8F%E8%A7%88%E5%99%A8/" itemprop="url" rel="index"><span itemprop="name">浏览器</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="Chromium如何显示页面"><a href="#Chromium如何显示页面" class="headerlink" title="Chromium如何显示页面"></a>Chromium如何显示页面</h1><p><a target="_blank" rel="noopener" href="http://www.chromium.org/developers/design-documents/displaying-a-web-page-in-chrome">原文地址</a></p>
<p><a target="_blank" rel="noopener" href="https://github.com/nifei/memo/blob/master/Chrome/How-Chromium-Displays-Web-Pages.md">译文地址</a></p>
<p>文档从下向上讲述了Chromium怎样显示网页. 确保你看过<a target="_blank" rel="noopener" href="http://www.chromium.org/developers/design-documents/multi-process-architecture">multi-process architecture</a>设计文档了. 理解主要组件的阻塞流程很重要. <a target="_blank" rel="noopener" href="http://www.chromium.org/developers/design-documents/multi-process-resource-loading">multi-process resource loading</a>这一篇讲怎样从互联网上获取页面可能也有帮助. </p>
<h2 id="概念上的应用层"><a href="#概念上的应用层" class="headerlink" title="概念上的应用层"></a>概念上的应用层</h2><p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190810152034.png" alt="alt text" title="(The original Google Doc for this illustration is http://goo.gl/MsEJX which is open for editing by any @chromium.org)"> </p>
<p>每个方块代表一个应用层. 应用层不应该知道或者依赖更高层的应用. </p>
<ul>
<li><p><strong>Webkit:</strong> 在Safari, Chromium和其他基于Webkit的浏览器中使用的渲染引擎. <strong>Port</strong>是Webkit的一部分, 它整合平台相关的系统服务, 像资源加载或者图片什么的. </p>
</li>
<li><p><strong>Glue:</strong> Webkit类型到Chromium类型的转换. 这是”Webkit嵌套层”. 这是Chromium和test_shell(测试Webkit用的)这两个浏览器的基础. </p>
</li>
<li><p><strong>Renderer/Render Host:</strong> Chromium的”多进程嵌入层”. 它在进程边检之间代理通知和指令. </p>
</li>
<li><p><strong>WebContents:</strong> 内容模块的可重用组件, 主类. 可嵌套, 允许Html到view的多进程渲染. 详见<a target="_blank" rel="noopener" href="http://www.chromium.org/developers/content-module">Content module pages</a>. </p>
</li>
<li><p><strong>Brwoser:</strong> 浏览器窗口, 包括多个WebContentses. </p>
</li>
<li><p><strong>Tab Helpers:</strong> 可以附加到WebContents上的独立对象(通过WebContentsUserData mixin). 浏览器往它持有的WebContentses(一个网站图标, 一个信息栏, 等)上附加一个这些玩意的混搭. </p>
</li>
</ul>
<h2 id="Webkit"><a href="#Webkit" class="headerlink" title="Webkit"></a>Webkit</h2><p>我们用<a target="_blank" rel="noopener" href="http://webkit.org/">Webkit</a>开源工程来排版网页. 代码从Apple拉过来放在路径<code>/third_party/WebKit</code>下. Webkit主要是由代表核心排版功能的”WebCore”和运行JavaScript的”JavaScriptCore”组成. 我们只用JavaScriptCore来跑测试, 通常使用性能更好的V8 JavaScrip引擎来代替它. 我们实际上并不使用苹果称为”WebKit”的那一层, 那个是WebCore和诸如Safari的OS X应用的嵌入式API. 为了方便我们一般把苹果的代码称作”WebKit”. </p>
<h3 id="The-WebKit-port"><a href="#The-WebKit-port" class="headerlink" title="The WebKit port"></a>The WebKit port</h3><p>在最底层我们有我们的WebKit “port”. 这是需要的特定平台和平台独立的WebCore代码交互的功能的实现. 文件在WebKit tree中, 通常在<code>chromium</code>路径或者Chromium后缀的文件. 端口(port)的大部分不限定OS: 可以把它想成WebCore的”Chromium端口”. 有些部分例如字体渲染必需根据平台分别处理. </p>
<ul>
<li><p>网络通信是由<a target="_blank" rel="noopener" href="http://www.chromium.org/developers/design-documents/multi-process-resource-loading">multi-process resource loading</a>系统处理的, 而不是从渲染进程直接移交操作系统. </p>
</li>
<li><p>图片使用Android开发的Skia图片库. 这个跨平台的图片库处理所有图片和图片权限, 除了文字. Skia放在<code>/third_party/skia</code>下. 图片操作的切入点是<code>/webkit/port/platform/graphics/GraphicsContextSkia.cpp</code>. 它会用到同路径下和<code>/base/gfx</code>下的其他文件.</p>
</li>
</ul>
<h2 id="The-WebKit-glue"><a href="#The-WebKit-glue" class="headerlink" title="The WebKit glue"></a>The WebKit glue</h2><p>Chromium应用使用和第三方WebKit代码不同的类型, 编码风格和代码排版. WebKit “glue” 使用Google代码风格和类型提供了更方便的嵌入API(例子, 用<code>std::string</code>代替<code>WebCore::string</code>, <code>GURL</code>代替<code>KURL</code>). 黏合代码(glue code)置于<code>/webkit/glue</code>. 黏合对象和WebKit对象命名通常类似, 以”Web”打头. 例如<code>WebCore::Frame</code>变成<code>WebFrame</code>. </p>
<p>WebKit”黏合”层把Chromium代码基础的其他部分和WebCore的数据类型隔离开来, 将WebCore的变化对Chromium代码基础(基线?)的影响降到最小. Chromium从不直接使用WebCore的数据类型. 加到WebKit的API在需要调用某个WebCore对象的时候为了Chromium黏合. </p>
<p>“test shell”应用是个用于测试WebKit端口和黏合代码的极简的浏览器. 它像chromium一样使用相同的glue接口和WebKit通信. 开发者不必使用复杂的浏览器特性, 线程和进程就可以测试新代码. 这个应用也用来跑WebKit的自动化测试. 但是”test shell”不像Chromium一样以多进程的方式使用WebKit. 内容模块被放置在一个叫做”content shell”的应用中立即跑测试. </p>
<h2 id="渲染进程"><a href="#渲染进程" class="headerlink" title="渲染进程"></a>渲染进程</h2><p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190810152243.png" alt="alt text"></p>
<p>Chromium的渲染进程使用黏合接口嵌入在WebKit端口中. 代码不多: 主要任务是作为连接浏览器的IPC通道的渲染器端. </p>
<p>渲染器中最重要的类是<code>RenderView</code>, 在<code>/content/renderer/render_view_impl.cc</code>. 这个对象代表网页. 它处理所有来往浏览器进程的跳转相关的指令. 它继承自<code>RenderWidget</code>, <code>RenderWidget</code>提供绘制和输入事件处理. <code>RenderView</code>通过全局的<code>RenderProcess</code>对象(每个渲染进程)和浏览器进程通信. </p>
<p><strong>FAQ:RenderWidget和RenderView的区别?</strong> <code>RenderWidget</code>通过实现黏合层叫做<code>WebWidgetDelegate</code>的抽象接口映射到<code>WebCore::Widget</code>对象. 基本上就是一个接收输入事件,我们要往上绘制的窗口. <code>RenderView</code>继承自<code>RenderWidget</code>, 是窗口或页面的内容. <code>RenderView</code>除了绘制和接收输入事件之外还要处理跳转指令. 只有一种情况下一个<code>RenderWidget</code>不是一个<code>RenderView</code>: 网页上的选择框. 那种有一个向下的箭头, 会弹出来一个选择列表的框框(combo box). 选择框需要使用原生窗口渲染以保证在最顶层, 还可能超过窗口边界. 这样的窗口也需要绘制和接收事件但是并不是一个”网页”(<code>RenderView</code>). </p>
<h3 id="渲染器的线程"><a href="#渲染器的线程" class="headerlink" title="渲染器的线程"></a>渲染器的线程</h3><p>一个渲染器有两个线程(见<a target="_blank" rel="noopener" href="http://www.chromium.org/developers/design-documents/multi-process-architecture">multi-process architecture</a>的流程图, 或者见<a target="_blank" rel="noopener" href="http://www.chromium.org/developers/design-documents/threading">threading in Chromium</a>怎样编程). 主要对象像<code>RenderView</code>和所有的WebKit 代码会跑在渲染线程中. 在所有其他事情之中这允许我们同步地从渲染器向浏览器发送消息. 少数几个操作要求来自浏览器的结果要继续进行. 一个例子是JavaScript要求的时候获取页面cookie. 渲染器线程会阻塞, 主线程排队处理所有的消息直到找到正确的回应. 这期间受到的所有消息会按顺序放在渲染器线程的后面. </p>
<h2 id="浏览器进程"><a href="#浏览器进程" class="headerlink" title="浏览器进程"></a>浏览器进程</h2><p><img src="https://picgoimg.oss-cn-beijing.aliyuncs.com/20190810152635.png" alt="alt text"></p>
<h3 id="底层浏览器进程对象"><a href="#底层浏览器进程对象" class="headerlink" title="底层浏览器进程对象"></a>底层浏览器进程对象</h3><p>所有和渲染器进程之间的<a target="_blank" rel="noopener" href="http://www.chromium.org/developers/design-documents/inter-process-communication">IPC</a>通信都是在浏览器的I/O线程中完成的. 这个线程还处理避免介入UI的<a target="_blank" rel="noopener" href="http://www.chromium.org/developers/design-documents/multi-process-resource-loading">network communication</a>. </p>
<p><code>RenderProcessHost</code>在主线程(UI线程)中初始化的时候会创建新的渲染器进程和带有通到渲染器的命名的管道<code>ChannelProxy</code>IPC对象. 这个对象(ChannelProxy)在浏览器的I/O线程中运行, 监听渲染器的通道, 自动把所有消息发送回UI线程的<code>RenderprocessHost</code>. 这个通道(ChannelProxy)上会安装一个<code>ResourceMessageFilter</code>, 滤掉能在I/O线程中直接处理的常规信息像网络请求什么的. 过滤的实现在<code>ResourceMessageFilter::OnMessageReceived</code>. </p>
<p>UI线程的<code>RenderProcessHost</code>负责分配每个view的信息到合适的<code>RenderViewHost</code>(介个会自己处理一些和view没有关系的信息)去. 消息分配实现在<code>RenderProcessHost::OnMessageReceived</code>. </p>
<h3 id="上层浏览器进程对象"><a href="#上层浏览器进程对象" class="headerlink" title="上层浏览器进程对象"></a>上层浏览器进程对象</h3><p>具体的view的消息会被传到<code>RenderViewHost::OnMessageReceived</code>. 多数消息在此处理, 其他的在基类<code>RenderWidgetHOST</code>中处理. 这两个对应渲染器中的<code>RenderView</code>和<code>RenderWidget</code>(见上面的”The Render Process”). 每个平台有一个view的类(<code>RenderWidgetHostView[Aura|Gtk|Mac|Win]</code>)来实现原生的view系统的整合. </p>
<p>在<code>RenderView/Widget</code>上层是<code>WebContents</code>对象, 多数消息最终由这个对象的一个函数调用来处理. <code>WebContents</code>表现网页内容. 是内容模块的顶层对象, 负责在一个矩形视图中显示网页. 详见<a target="_blank" rel="noopener" href="http://www.chromium.org/developers/content-module">content module</a>. </p>
<p><code>WebContents</code>对象包含在<code>TabContentsWrapper</code>中. 在<code>chrome/</code>下面, 负责一个tab. </p>
<h2 id="说明用例子"><a href="#说明用例子" class="headerlink" title="说明用例子"></a>说明用例子</h2><p>其他的跳转和启动的例子在<a target="_blank" rel="noopener" href="http://www.chromium.org/developers/how-tos/getting-around-the-chrome-source-code">Getting Around the Chromium Source Code</a>中. </p>
<h3 id="“set-cursor”消息的生命周期"><a href="#“set-cursor”消息的生命周期" class="headerlink" title="“set cursor”消息的生命周期"></a>“set cursor”消息的生命周期</h3><p>设置鼠标是从渲染器发送到浏览器的典型消息的一个例子. 在渲染器中, 发生了这些事情. </p>
<ul>
<li><p>Set cursor 消息由WebKit内部生成, 通常是用来相应输入事件. Set cursor消息始于<code>RenderWidget::SetCursor in content/renderer/render_widget.cc</code>. </p>
</li>
<li><p>(Set Cursor消息)会调用<code>RenderWidget::Send</code>来分配消息. <code>RenderView</code>也用这个方法来向浏览器发送消息, 它会调用<code>RenderThread::Send</code>. </p>
</li>
<li><p>会调到<code>IPC::SyncChannel</code>, 函数内部会代理发送这个消息到渲染器的主线程, 放在浏览器命名通道的后面. </p>
</li>
</ul>
<p>然后浏览器会接管过来: </p>
<ul>
<li><p><code>RenderProcessHost</code>中的<code>IPC::ChannelProxy</code>会接收到所有浏览器I/O线程的消息. 它会先把消息发送到<code>ResourceMessageFilter</code>分派网络请求和I/O线程的直接相关消息. 因为Set Cursor这个消息不会被过滤掉, 它会继续发送到浏览器的UI线程(<code>IPC;ChannelProxy</code>会在内部处理). </p>
</li>
<li><p> 在<code>content/browser/renderer_host/render_process_host_impl.cc</code>中<code>RenderProcessHost::OnMessageReceived</code>拿到相应渲染进程的所有view的消息. 它会直接处理几种类型的消息, 剩下的分发到和发送消息的<code>RenderView</code>相应的<code>RenderViewHost</code>上. </p>
</li>
<li><p><code>RenderViewHost</code>没有处理的消息都会发送到<code>RenderWidgetHost</code>中处理, 包括Set cursor消息. </p>
</li>
<li><p><code>content/browser/renderer_host/render_view_host_impl.cc</code>中的消息表最终在<code>RenderWidgetHost::OnMsgSetCursor</code>中收到消息, 调用相应的UI函数来设置鼠标. </p>
</li>
</ul>
<h3 id="“mouse-click”消息的周期"><a href="#“mouse-click”消息的周期" class="headerlink" title="“mouse click”消息的周期"></a>“mouse click”消息的周期</h3><p>发送鼠标点击是从浏览器发送到渲染器的典型消息. </p>
<ul>
<li><p>浏览器的UI线程通过<code>RenderWidgetHostViewWin::OnMouseEvent</code>收到Windows消息, 然后调用同一个类里的<code>ForwardMouseEventToRenderer </code>. </p>
</li>
<li><p>发送函数将输入事件打包成一个跨平台的<code>WebMouseEvent</code>最后发送到相关的<code>RenderWidgetHost</code>. </p>
</li>
<li><p><code>RenderWidgetHost::ForwardInputEvent</code>创建一个IPC消息<code>ViewMsg_HandleInputEvent</code>, 转播<code>WebInputEvent</code>给它, 然后调用<code>RenderWidgetHost::Send</code>. </p>
</li>
<li><p>这样做只是转播<code>RenderProcessHost::Send</code>函数, <code>RenderProcessHost::Send</code>会按顺序发送消息到<code>IPC::ChannelProxy</code>. </p>
</li>
<li><p>在内部<code>IPC::ChannelProxy</code>把消息托管发送到浏览器的I/O线程, 并且写到对应渲染器的命名管道中. </p>
</li>
</ul>
<p>注意<code>WebContents</code>会创建许多其他类型的消息, 特别是跳转一类的. 这些消息通过类似的途径从<code>WebContents</code>传递到<code>RenderViewHost</code>. </p>
<p>渲染器接管: </p>
<ul>
<li><p>渲染器主线程中的<code>IPC::Chanel</code>读取浏览器发送的消息, <code>IPC::ChannelProxy</code>把消息委托到渲染器线程. </p>
</li>
<li><p><code>RenderView::OnMessageReceived</code>拿到消息. 很多种消息在此直接处理. click消息不是, 它会(和其他未被处理的消息一起)发送到<code>RenderWidget::OnMessageReceived</code>, 后者按顺序发送到<code>RenderWidget::OnHandelInputEvent</code>. </p>
</li>
<li><p>输入事件被发送到<code>WebWidgetImpl::HandleInputEvent</code>, <code>WebWidgetImpl::HandleInputEvent</code>把它转换成一个WebKit<code>PlatformMouseEvent</code>类型然后发送给WebKit离得<code>WebCore::Widget</code>类. </p>
</li>
</ul>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh">
    <link itemprop="mainEntityOfPage" href="https://suyuhuan.gitee.io/yuwanzi.io/2018/03/16/2018-03-16-map_family/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/yuwanzi.io/images/avatar.gif">
      <meta itemprop="name" content="玉丸子">
      <meta itemprop="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="玉丸子 | Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/yuwanzi.io/2018/03/16/2018-03-16-map_family/" class="post-title-link" itemprop="url">Map大家族的那点事儿</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Veröffentlicht am</span>

      <time title="Erstellt: 2018-03-16 18:00:00" itemprop="dateCreated datePublished" datetime="2018-03-16T18:00:00+08:00">2018-03-16</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">Bearbeitet am</span>
        <time title="Geändert am: 2020-11-07 08:58:17" itemprop="dateModified" datetime="2020-11-07T08:58:17+08:00">2020-11-07</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">in</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%90%8E%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">后端</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%90%8E%E7%AB%AF/Java/" itemprop="url" rel="index"><span itemprop="name">Java</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h3 id="Map"><a href="#Map" class="headerlink" title="Map"></a>Map</h3><hr>
<p>Map是一种用于快速查找的数据结构，它以键值对的形式存储数据，每一个键都是唯一的，且对应着一个值，如果想要查找Map中的数据，只需要传入一个键，Map会对键进行匹配并返回键所对应的值，可以说Map其实就是一个存放键值对的集合。Map被各种编程语言广泛使用，只不过在名称上可能会有些混淆，像Python中叫做字典（Dictionary），也有些语言称其为关联数组（Associative Array），但其实它们都是一样的，都是一个存放键值对的集合。至于Java中经常用到的HashMap也是Map的一种，它被称为散列表，关于散列表的细节我会在本文中解释HashMap的源码时提及。</p>
<p>Java还提供了一种与Map密切相关的数据结构：Set，它是数学意义上的集合，特性如下：</p>
<ul>
<li><p>无序性：一个集合中，每个元素的地位都是相同的，元素之间也都是无序的。不过Java中也提供了有序的Set，这点倒是没有完全遵循。</p>
</li>
<li><p>互异性：一个集合中，任何两个元素都是不相同的。</p>
</li>
<li><p>确定性：给定一个集合以及其任一元素，该元素属于或者不属于该集合是必须可以确定的。</p>
</li>
</ul>
<p>很明显，Map中的key就很符合这些特性，Set的实现其实就是在内部使用Map。例如，HashSet就定义了一个类型为HashMap的成员变量，向HashSet添加元素a，等同于向它内部的HashMap添加了一个key为a，value为一个Object对象的键值对，这个Object对象是HashSet的一个常量，它是一个虚拟值，没有什么实际含义，源码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> HashMap&lt;E,Object&gt; map;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Dummy value to associate with an Object in the backing Map</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Object PRESENT = <span class="keyword">new</span> Object();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">add</span><span class="params">(E e)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> map.put(e, PRESENT)==<span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>小插曲过后，让我们接着说Map，它是JDK的一个顶级接口，提供了三种集合视图（Collection Views）：包含所有key的集合、包含所有value的集合以及包含所有键值对的集合，Map中的元素顺序与它所返回的集合视图中的元素的迭代顺序相关，也就是说，Map本身是不保证有序性的，当然也有例外，比如TreeMap就对有序性做出了保证，这主要因为它是基于红黑树实现的。</p>
<p>所谓的集合视图就是由集合本身提供的一种访问数据的方式，同时对视图的任何修改也会影响到集合。好比<code>Map.keySet()</code>返回了它包含的key的集合，如果你调用了<code>Map.remove(key)</code>那么<code>keySet.contains(key)</code>也将返回<code>false</code>，再比如说<code>Arrays.asList(T)</code>可以把一个数组封装成一个List，这样你就可以通过List的API来访问和操作这些数据，如下列示例代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">String[] strings = &#123;<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>&#125;;</span><br><span class="line">List&lt;String&gt; list = Arrays.asList(strings);</span><br><span class="line">System.out.println(list.get(<span class="number">0</span>)); <span class="comment">// &quot;a&quot;</span></span><br><span class="line">strings[<span class="number">0</span>] = <span class="string">&quot;d&quot;</span>;</span><br><span class="line">System.out.println(list.get(<span class="number">0</span>)); <span class="comment">// &quot;d&quot;</span></span><br><span class="line">list.set(<span class="number">0</span>, <span class="string">&quot;e&quot;</span>);</span><br><span class="line">System.out.println(strings[<span class="number">0</span>]); <span class="comment">// &quot;e&quot;</span></span><br></pre></td></tr></table></figure>
<p>是不是感觉很神奇，其实<code>Arrays.asList()</code>只是将传入的数组与<code>Arrays</code>中的一个内部类<code>ArrayList</code>（注意，它与<code>java.util</code>包下的<code>ArrayList</code>不是同一个）做了一个”绑定“，在调用<code>get()</code>时会直接根据下标返回数组中的元素，而调用<code>set()</code>时也会直接修改数组中对应下标的元素。相对于直接复制来说，集合视图的优点是内存利用率更高，假设你有一个数组，又很想使用List的API来操作它，那么你不用new一个<code>ArrayList</code>以拷贝数组中的元素，只需要一点额外的内存（通过<code>Arrays.ArrayList</code>对数组进行封装），原始数据依然是在数组中的，并不会复制成多份。</p>
<p>Map接口规范了Map数据结构的通用API（也含有几个用于简化操作的default方法，default是JDK8的新特性，它是接口中声明的方法的默认实现，即非抽象方法）并且还在内部定义了Entry接口（键值对的实体类），在JDK中提供的所有Map数据结构都实现了Map接口，下面为Map接口的源码（代码中的注释太长了，基本都是些实现的规范，为了篇幅我就尽量省略了）。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> java.util;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.function.BiConsumer;</span><br><span class="line"><span class="keyword">import</span> java.util.function.BiFunction;</span><br><span class="line"><span class="keyword">import</span> java.util.function.Function;</span><br><span class="line"><span class="keyword">import</span> java.io.Serializable;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Map</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 查询操作</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回这个Map中所包含的键值对的数量，如果大于Integer.MAX_VALUE，</span></span><br><span class="line"><span class="comment">     * 则应该返回Integer.MAX_VALUE。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">size</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Map是否为空。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">boolean</span> <span class="title">isEmpty</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment"> 	 * Map中是否包含key，如果是返回true，否则false。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">boolean</span> <span class="title">containsKey</span><span class="params">(Object key)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Map中是否包含value，如果是返回true，否则false。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">boolean</span> <span class="title">containsValue</span><span class="params">(Object value)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据key查找value，如果Map不包含该key，则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">V <span class="title">get</span><span class="params">(Object key)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 修改操作</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 添加一对键值对，如果Map中已含有这个key，那么新value将覆盖掉旧value，</span></span><br><span class="line"><span class="comment">     * 并返回旧value，如果Map中之前没有这个key，那么返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">V <span class="title">put</span><span class="params">(K key, V value)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 删除指定key并返回之前的value，如果Map中没有该key，则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">V <span class="title">remove</span><span class="params">(Object key)</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 批量操作</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 将指定Map中的所有键值对批量添加到当前Map。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">putAll</span><span class="params">(Map&lt;? extends K, ? extends V&gt; m)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 删除Map中所有的键值对。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">clear</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 集合视图</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回包含Map中所有key的Set，对该视图的所有修改操作会对Map产生同样的影响，反之亦然。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">Set&lt;K&gt; <span class="title">keySet</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回包含Map中所有value的集合，对该视图的所有修改操作会对Map产生同样的影响，反之亦然。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">Collection&lt;V&gt; <span class="title">values</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回包含Map中所有键值对的Set，对该视图的所有修改操作会对Map产生同样的影响，反之亦然。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Set&lt;Map.Entry&lt;K, V&gt;&gt; entrySet();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Entry代表一对键值对，规范了一些基本函数以及几个已实现的类函数（各种比较器）。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="class"><span class="keyword">interface</span> <span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">       </span><br><span class="line">        <span class="function">K <span class="title">getKey</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">        <span class="function">V <span class="title">getValue</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">        <span class="function">V <span class="title">setValue</span><span class="params">(V value)</span></span>;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object o)</span></span>;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">static</span> &lt;K extends Comparable&lt;? <span class="keyword">super</span> K&gt;, V&gt; Comparator&lt;Map.Entry&lt;K,V&gt;&gt; comparingByKey() &#123;</span><br><span class="line">            <span class="keyword">return</span> (Comparator&lt;Map.Entry&lt;K, V&gt;&gt; &amp; Serializable)</span><br><span class="line">                (c1, c2) -&gt; c1.getKey().compareTo(c2.getKey());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">static</span> &lt;K, V extends Comparable&lt;? <span class="keyword">super</span> V&gt;&gt; Comparator&lt;Map.Entry&lt;K,V&gt;&gt; comparingByValue() &#123;</span><br><span class="line">            <span class="keyword">return</span> (Comparator&lt;Map.Entry&lt;K, V&gt;&gt; &amp; Serializable)</span><br><span class="line">                (c1, c2) -&gt; c1.getValue().compareTo(c2.getValue());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">static</span> &lt;K, V&gt; Comparator&lt;Map.Entry&lt;K, V&gt;&gt; comparingByKey(Comparator&lt;? <span class="keyword">super</span> K&gt; cmp) &#123;</span><br><span class="line">            Objects.requireNonNull(cmp);</span><br><span class="line">            <span class="keyword">return</span> (Comparator&lt;Map.Entry&lt;K, V&gt;&gt; &amp; Serializable)</span><br><span class="line">                (c1, c2) -&gt; cmp.compare(c1.getKey(), c2.getKey());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">static</span> &lt;K, V&gt; Comparator&lt;Map.Entry&lt;K, V&gt;&gt; comparingByValue(Comparator&lt;? <span class="keyword">super</span> V&gt; cmp) &#123;</span><br><span class="line">            Objects.requireNonNull(cmp);</span><br><span class="line">            <span class="keyword">return</span> (Comparator&lt;Map.Entry&lt;K, V&gt;&gt; &amp; Serializable)</span><br><span class="line">                (c1, c2) -&gt; cmp.compare(c1.getValue(), c2.getValue());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 比较和hashing</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 将指定的对象与此Map进行比较是否相等。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object o)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回此Map的hash code。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 默认方法（非抽象方法）</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据key查找value，如果该key不存在或等于null则返回defaultValue。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> V <span class="title">getOrDefault</span><span class="params">(Object key, V defaultValue)</span> </span>&#123;</span><br><span class="line">        V v;</span><br><span class="line">        <span class="keyword">return</span> (((v = get(key)) != <span class="keyword">null</span>) || containsKey(key)) ? v : defaultValue;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 遍历Map并对每个键值对执行指定的操作（action）。</span></span><br><span class="line"><span class="comment">     * BiConsumer是一个函数接口（具有一个抽象方法的接口，用于支持Lambda），</span></span><br><span class="line"><span class="comment">     * 它代表了一个接受两个输入参数的操作，且不返回任何结果。</span></span><br><span class="line"><span class="comment">     * 至于它奇怪的名字，根据Java中的其他函数接口的命名规范，Bi应该是Binary的缩写，意思是二元的。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> <span class="keyword">void</span> <span class="title">forEach</span><span class="params">(BiConsumer&lt;? <span class="keyword">super</span> K, ? <span class="keyword">super</span> V&gt; action)</span> </span>&#123;</span><br><span class="line">        Objects.requireNonNull(action);</span><br><span class="line">        <span class="keyword">for</span> (Map.Entry&lt;K, V&gt; entry : entrySet()) &#123;</span><br><span class="line">            K k;</span><br><span class="line">            V v;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                k = entry.getKey();</span><br><span class="line">                v = entry.getValue();</span><br><span class="line">            &#125; <span class="keyword">catch</span>(IllegalStateException ise) &#123;</span><br><span class="line">                <span class="comment">// this usually means the entry is no longer in the map.</span></span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException(ise);</span><br><span class="line">            &#125;</span><br><span class="line">            action.accept(k, v);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** </span></span><br><span class="line"><span class="comment">     * 遍历Map，然后调用传入的函数function生成新value对旧value进行替换。</span></span><br><span class="line"><span class="comment">     * BiFunction同样是一个函数接口，它接受两个输入参数并且返回一个结果。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> <span class="keyword">void</span> <span class="title">replaceAll</span><span class="params">(BiFunction&lt;? <span class="keyword">super</span> K, ? <span class="keyword">super</span> V, ? extends V&gt; function)</span> </span>&#123;</span><br><span class="line">        Objects.requireNonNull(function);</span><br><span class="line">        <span class="keyword">for</span> (Map.Entry&lt;K, V&gt; entry : entrySet()) &#123;</span><br><span class="line">            K k;</span><br><span class="line">            V v;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                k = entry.getKey();</span><br><span class="line">                v = entry.getValue();</span><br><span class="line">            &#125; <span class="keyword">catch</span>(IllegalStateException ise) &#123;</span><br><span class="line">                <span class="comment">// this usually means the entry is no longer in the map.</span></span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException(ise);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// ise thrown from function is not a cme.</span></span><br><span class="line">            v = function.apply(k, v);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                entry.setValue(v);</span><br><span class="line">            &#125; <span class="keyword">catch</span>(IllegalStateException ise) &#123;</span><br><span class="line">                <span class="comment">// this usually means the entry is no longer in the map.</span></span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException(ise);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 如果指定的key不存在或者关联的value为null，则添加键值对。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> V <span class="title">putIfAbsent</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">        V v = get(key);</span><br><span class="line">        <span class="keyword">if</span> (v == <span class="keyword">null</span>) &#123;</span><br><span class="line">            v = put(key, value);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> v;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 当指定key关联的value与传入的参数value相等时删除该key。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> <span class="keyword">boolean</span> <span class="title">remove</span><span class="params">(Object key, Object value)</span> </span>&#123;</span><br><span class="line">        Object curValue = get(key);</span><br><span class="line">        <span class="keyword">if</span> (!Objects.equals(curValue, value) ||</span><br><span class="line">            (curValue == <span class="keyword">null</span> &amp;&amp; !containsKey(key))) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        remove(key);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 当指定key关联的value与oldValue相等时，使用newValue进行替换。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> <span class="keyword">boolean</span> <span class="title">replace</span><span class="params">(K key, V oldValue, V newValue)</span> </span>&#123;</span><br><span class="line">        Object curValue = get(key);</span><br><span class="line">        <span class="keyword">if</span> (!Objects.equals(curValue, oldValue) ||</span><br><span class="line">            (curValue == <span class="keyword">null</span> &amp;&amp; !containsKey(key))) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        put(key, newValue);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 当指定key关联到某个value时进行替换。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> V <span class="title">replace</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">        V curValue;</span><br><span class="line">        <span class="keyword">if</span> (((curValue = get(key)) != <span class="keyword">null</span>) || containsKey(key)) &#123;</span><br><span class="line">            curValue = put(key, value);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> curValue;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 当指定key没有关联到一个value或者value为null时，调用mappingFunction生成值并添加键值对到Map。</span></span><br><span class="line"><span class="comment">     * Function是一个函数接口，它接受一个输入参数并返回一个结果，如果mappingFunction返回的结果</span></span><br><span class="line"><span class="comment">     * 也为null，那么将不会调用put。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> V <span class="title">computeIfAbsent</span><span class="params">(K key,</span></span></span><br><span class="line"><span class="function"><span class="params">            Function&lt;? <span class="keyword">super</span> K, ? extends V&gt; mappingFunction)</span> </span>&#123;</span><br><span class="line">        Objects.requireNonNull(mappingFunction);</span><br><span class="line">        V v;</span><br><span class="line">        <span class="keyword">if</span> ((v = get(key)) == <span class="keyword">null</span>) &#123;</span><br><span class="line">            V newValue;</span><br><span class="line">            <span class="keyword">if</span> ((newValue = mappingFunction.apply(key)) != <span class="keyword">null</span>) &#123;</span><br><span class="line">                put(key, newValue);</span><br><span class="line">                <span class="keyword">return</span> newValue;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> v;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 当指定key关联到一个value并且不为null时，调用remappingFunction生成newValue，</span></span><br><span class="line"><span class="comment">     * 如果newValue不为null，那么进行替换，否则删除该key。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> V <span class="title">computeIfPresent</span><span class="params">(K key,</span></span></span><br><span class="line"><span class="function"><span class="params">            BiFunction&lt;? <span class="keyword">super</span> K, ? <span class="keyword">super</span> V, ? extends V&gt; remappingFunction)</span> </span>&#123;</span><br><span class="line">        Objects.requireNonNull(remappingFunction);</span><br><span class="line">        V oldValue;</span><br><span class="line">        <span class="keyword">if</span> ((oldValue = get(key)) != <span class="keyword">null</span>) &#123;</span><br><span class="line">            V newValue = remappingFunction.apply(key, oldValue);</span><br><span class="line">            <span class="keyword">if</span> (newValue != <span class="keyword">null</span>) &#123;</span><br><span class="line">                put(key, newValue);</span><br><span class="line">                <span class="keyword">return</span> newValue;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                remove(key);</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * remappingFunction根据key与其相关联的value生成newValue，</span></span><br><span class="line"><span class="comment">     * 当newValue等于null时删除该key，否则添加或者替换旧的映射。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> V <span class="title">compute</span><span class="params">(K key,</span></span></span><br><span class="line"><span class="function"><span class="params">            BiFunction&lt;? <span class="keyword">super</span> K, ? <span class="keyword">super</span> V, ? extends V&gt; remappingFunction)</span> </span>&#123;</span><br><span class="line">        Objects.requireNonNull(remappingFunction);</span><br><span class="line">        V oldValue = get(key);</span><br><span class="line"></span><br><span class="line">        V newValue = remappingFunction.apply(key, oldValue);</span><br><span class="line">        <span class="keyword">if</span> (newValue == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="comment">// delete mapping</span></span><br><span class="line">            <span class="keyword">if</span> (oldValue != <span class="keyword">null</span> || containsKey(key)) &#123;</span><br><span class="line">                <span class="comment">// something to remove</span></span><br><span class="line">                remove(key);</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// nothing to do. Leave things as they were.</span></span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// add or replace old mapping</span></span><br><span class="line">            put(key, newValue);</span><br><span class="line">            <span class="keyword">return</span> newValue;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 当指定key没有关联到一个value或者value为null，将它与传入的参数value</span></span><br><span class="line"><span class="comment">     * 进行关联。否则，调用remappingFunction生成newValue并进行替换。</span></span><br><span class="line"><span class="comment">     * 如果，newValue等于null，那么删除该key。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> V <span class="title">merge</span><span class="params">(K key, V value,</span></span></span><br><span class="line"><span class="function"><span class="params">            BiFunction&lt;? <span class="keyword">super</span> V, ? <span class="keyword">super</span> V, ? extends V&gt; remappingFunction)</span> </span>&#123;</span><br><span class="line">        Objects.requireNonNull(remappingFunction);</span><br><span class="line">        Objects.requireNonNull(value);</span><br><span class="line">        V oldValue = get(key);</span><br><span class="line">        V newValue = (oldValue == <span class="keyword">null</span>) ? value :</span><br><span class="line">                   remappingFunction.apply(oldValue, value);</span><br><span class="line">        <span class="keyword">if</span>(newValue == <span class="keyword">null</span>) &#123;</span><br><span class="line">            remove(key);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            put(key, newValue);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> newValue;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>需要注意一点，这些default方法都是非线程安全的，任何保证线程安全的扩展类都必须重写这些方法，例如ConcurrentHashMap。</p>
<p>下图为Map的继承关系结构图，它也是本文接下来将要分析的Map实现类的大纲，这些实现类都是比较常用的，在JDK中Map的实现类有几十个，大部分都是我们用不到的，限于篇幅原因就不一一讲解了（本文包含许多源码与对实现细节的分析，建议读者抽出一段连续的空闲时间静下心来慢慢阅读）。</p>
<p><img src="http://wx4.sinaimg.cn/large/63503acbly1fpu3a4q9ewj20hc08wjre.jpg"></p>
<blockquote>
<p>本文作者为<a target="_blank" rel="noopener" href="https://github.com/SylvanasSun">SylvanasSun(sylvanas.sun@gmail.com)</a>，首发于<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/">SylvanasSun’s Blog</a>。<br>原文链接：<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/2018/03/16/2018-03-16-map_family/">https://sylvanassun.github.io/2018/03/16/2018-03-16-map_family/</a><br>（转载请务必保留本段声明，并且保留超链接。）</p>
</blockquote>
<h3 id="AbstractMap"><a href="#AbstractMap" class="headerlink" title="AbstractMap"></a>AbstractMap</h3><hr>
<p>AbstractMap是一个抽象类，它是Map接口的一个骨架实现，最小化实现了此接口提供的抽象函数。在Java的Collection框架中基本都遵循了这一规定，骨架实现在接口与实现类之间构建了一层抽象，其目的是为了复用一些比较通用的函数以及方便扩展，例如List接口拥有骨架实现AbstractList、Set接口拥有骨架实现AbstractSet等。</p>
<p>下面我们按照不同的操作类型来看看AbstractMap都实现了什么，首先是查询操作：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> java.util;</span><br><span class="line"><span class="keyword">import</span> java.util.Map.Entry;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">AbstractMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">implements</span> <span class="title">Map</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">AbstractMap</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Query Operations</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">size</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> entrySet().size();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 键值对的集合视图留给具体的实现类实现</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> Set&lt;Entry&lt;K,V&gt;&gt; entrySet();</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isEmpty</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> size() == <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 遍历entrySet，然后逐个进行比较。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">containsValue</span><span class="params">(Object value)</span> </span>&#123;</span><br><span class="line">        Iterator&lt;Entry&lt;K,V&gt;&gt; i = entrySet().iterator();</span><br><span class="line">        <span class="keyword">if</span> (value==<span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">while</span> (i.hasNext()) &#123;</span><br><span class="line">                Entry&lt;K,V&gt; e = i.next();</span><br><span class="line">                <span class="keyword">if</span> (e.getValue()==<span class="keyword">null</span>)</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (i.hasNext()) &#123;</span><br><span class="line">                Entry&lt;K,V&gt; e = i.next();</span><br><span class="line">                <span class="keyword">if</span> (value.equals(e.getValue()))</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 跟containsValue()同理，只不过比较的是key。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">containsKey</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">        Iterator&lt;Map.Entry&lt;K,V&gt;&gt; i = entrySet().iterator();</span><br><span class="line">        <span class="keyword">if</span> (key==<span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">while</span> (i.hasNext()) &#123;</span><br><span class="line">                Entry&lt;K,V&gt; e = i.next();</span><br><span class="line">                <span class="keyword">if</span> (e.getKey()==<span class="keyword">null</span>)</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (i.hasNext()) &#123;</span><br><span class="line">                Entry&lt;K,V&gt; e = i.next();</span><br><span class="line">                <span class="keyword">if</span> (key.equals(e.getKey()))</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 遍历entrySet，然后根据key取出关联的value。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">        Iterator&lt;Entry&lt;K,V&gt;&gt; i = entrySet().iterator();</span><br><span class="line">        <span class="keyword">if</span> (key==<span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">while</span> (i.hasNext()) &#123;</span><br><span class="line">                Entry&lt;K,V&gt; e = i.next();</span><br><span class="line">                <span class="keyword">if</span> (e.getKey()==<span class="keyword">null</span>)</span><br><span class="line">                    <span class="keyword">return</span> e.getValue();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (i.hasNext()) &#123;</span><br><span class="line">                Entry&lt;K,V&gt; e = i.next();</span><br><span class="line">                <span class="keyword">if</span> (key.equals(e.getKey()))</span><br><span class="line">                    <span class="keyword">return</span> e.getValue();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以发现这些操作都是依赖于函数<code>entrySet()</code>的，它返回了一个键值对的集合视图，由于不同的实现子类的Entry实现可能也是不同的，所以一般是在内部实现一个继承于AbstractSet且泛型为<code>Map.Entry</code>的内部类作为EntrySet，接下来是修改操作与批量操作：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Modification Operations</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 没有提供实现，子类必须重写该方法，否则调用put()会抛出异常。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> UnsupportedOperationException();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 遍历entrySet，先找到目标的entry，然后删除。</span></span><br><span class="line"><span class="comment"> *（还记得之前说过的吗，集合视图中的操作也会影响到实际数据）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">remove</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">    Iterator&lt;Entry&lt;K,V&gt;&gt; i = entrySet().iterator();</span><br><span class="line">    Entry&lt;K,V&gt; correctEntry = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">if</span> (key==<span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">while</span> (correctEntry==<span class="keyword">null</span> &amp;&amp; i.hasNext()) &#123;</span><br><span class="line">            Entry&lt;K,V&gt; e = i.next();</span><br><span class="line">            <span class="keyword">if</span> (e.getKey()==<span class="keyword">null</span>)</span><br><span class="line">                correctEntry = e;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (correctEntry==<span class="keyword">null</span> &amp;&amp; i.hasNext()) &#123;</span><br><span class="line">            Entry&lt;K,V&gt; e = i.next();</span><br><span class="line">            <span class="keyword">if</span> (key.equals(e.getKey()))</span><br><span class="line">                correctEntry = e;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    V oldValue = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">if</span> (correctEntry !=<span class="keyword">null</span>) &#123;</span><br><span class="line">        oldValue = correctEntry.getValue();</span><br><span class="line">        i.remove();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> oldValue;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// Bulk Operations</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 遍历参数m，然后将每一个键值对put到该Map中。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">putAll</span><span class="params">(Map&lt;? extends K, ? extends V&gt; m)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (Map.Entry&lt;? extends K, ? extends V&gt; e : m.entrySet())</span><br><span class="line">        put(e.getKey(), e.getValue());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 清空entrySet等价于清空该Map。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">clear</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    entrySet().clear();</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>AbstractMap并没有实现<code>put()</code>函数，这样做是为了考虑到也许会有不可修改的Map实现子类继承它，而对于一个可修改的Map实现子类则必须重写<code>put()</code>函数。</p>
<p>AbstractMap没有提供<code>entrySet()</code>的实现，但是却提供了<code>keySet()</code>与<code>values()</code>集合视图的默认实现，它们都是依赖于<code>entrySet()</code>返回的集合视图实现的，源码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * keySet和values是lazy的，它们只会在第一次请求视图时进行初始化，</span></span><br><span class="line"><span class="comment"> * 而且它们是无状态的，所以只需要一个实例（初始化一次）。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">transient</span> Set&lt;K&gt;        keySet;</span><br><span class="line"><span class="keyword">transient</span> Collection&lt;V&gt; values;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 返回一个AbstractSet的子类，可以发现它的行为都委托给了entrySet返回的集合视图</span></span><br><span class="line"><span class="comment"> * 与当前的AbstractMap实例，所以说它自身是无状态的。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Set&lt;K&gt; <span class="title">keySet</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    Set&lt;K&gt; ks = keySet;</span><br><span class="line">    <span class="keyword">if</span> (ks == <span class="keyword">null</span>) &#123;</span><br><span class="line">        ks = <span class="keyword">new</span> AbstractSet&lt;K&gt;() &#123;</span><br><span class="line">            <span class="function"><span class="keyword">public</span> Iterator&lt;K&gt; <span class="title">iterator</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> Iterator&lt;K&gt;() &#123;</span><br><span class="line">                    <span class="keyword">private</span> Iterator&lt;Entry&lt;K,V&gt;&gt; i = entrySet().iterator();</span><br><span class="line"></span><br><span class="line">                    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">hasNext</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                        <span class="keyword">return</span> i.hasNext();</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="function"><span class="keyword">public</span> K <span class="title">next</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                        <span class="keyword">return</span> i.next().getKey();</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                        i.remove();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">size</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> AbstractMap.<span class="keyword">this</span>.size();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isEmpty</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> AbstractMap.<span class="keyword">this</span>.isEmpty();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">clear</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                AbstractMap.<span class="keyword">this</span>.clear();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">contains</span><span class="params">(Object k)</span> </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> AbstractMap.<span class="keyword">this</span>.containsKey(k);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        keySet = ks;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ks;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 与keySet()基本一致，唯一的区别就是返回的是AbstractCollection的子类，</span></span><br><span class="line"><span class="comment"> * 主要是因为value不需要保持互异性。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Collection&lt;V&gt; <span class="title">values</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    Collection&lt;V&gt; vals = values;</span><br><span class="line">    <span class="keyword">if</span> (vals == <span class="keyword">null</span>) &#123;</span><br><span class="line">        vals = <span class="keyword">new</span> AbstractCollection&lt;V&gt;() &#123;</span><br><span class="line">            <span class="function"><span class="keyword">public</span> Iterator&lt;V&gt; <span class="title">iterator</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> Iterator&lt;V&gt;() &#123;</span><br><span class="line">                    <span class="keyword">private</span> Iterator&lt;Entry&lt;K,V&gt;&gt; i = entrySet().iterator();</span><br><span class="line"></span><br><span class="line">                    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">hasNext</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                        <span class="keyword">return</span> i.hasNext();</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="function"><span class="keyword">public</span> V <span class="title">next</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                        <span class="keyword">return</span> i.next().getValue();</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                        i.remove();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">size</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> AbstractMap.<span class="keyword">this</span>.size();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isEmpty</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> AbstractMap.<span class="keyword">this</span>.isEmpty();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">clear</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                AbstractMap.<span class="keyword">this</span>.clear();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">contains</span><span class="params">(Object v)</span> </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> AbstractMap.<span class="keyword">this</span>.containsValue(v);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        values = vals;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> vals;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>它还提供了两个Entry的实现类：SimpleEntry与SimpleImmutableEntry，这两个类的实现非常简单，区别也只是前者是可变的，而后者是不可变的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">eq</span><span class="params">(Object o1, Object o2)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> o1 == <span class="keyword">null</span> ? o2 == <span class="keyword">null</span> : o1.equals(o2);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleEntry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;</span></span><br><span class="line"><span class="class">    <span class="keyword">implements</span> <span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;, <span class="title">java</span>.<span class="title">io</span>.<span class="title">Serializable</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = -<span class="number">8499721149061103585L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> K key;</span><br><span class="line">    <span class="keyword">private</span> V value;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">SimpleEntry</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.key   = key;</span><br><span class="line">        <span class="keyword">this</span>.value = value;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">SimpleEntry</span><span class="params">(Entry&lt;? extends K, ? extends V&gt; entry)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.key   = entry.getKey();</span><br><span class="line">        <span class="keyword">this</span>.value = entry.getValue();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> K <span class="title">getKey</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> key;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> V <span class="title">getValue</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> value;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> V <span class="title">setValue</span><span class="params">(V value)</span> </span>&#123;</span><br><span class="line">        V oldValue = <span class="keyword">this</span>.value;</span><br><span class="line">        <span class="keyword">this</span>.value = value;</span><br><span class="line">        <span class="keyword">return</span> oldValue;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object o)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> Map.Entry))</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        Map.Entry&lt;?,?&gt; e = (Map.Entry&lt;?,?&gt;)o;</span><br><span class="line">        <span class="keyword">return</span> eq(key, e.getKey()) &amp;&amp; eq(value, e.getValue());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> (key   == <span class="keyword">null</span> ? <span class="number">0</span> :   key.hashCode()) ^</span><br><span class="line">               (value == <span class="keyword">null</span> ? <span class="number">0</span> : value.hashCode());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> key + <span class="string">&quot;=&quot;</span> + value;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 它与SimpleEntry的区别在于它是不可变的，value被final修饰，并且不支持setValue()。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleImmutableEntry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;</span></span><br><span class="line"><span class="class">    <span class="keyword">implements</span> <span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;, <span class="title">java</span>.<span class="title">io</span>.<span class="title">Serializable</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">7138329143949025153L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> K key;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> V value;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">SimpleImmutableEntry</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.key   = key;</span><br><span class="line">        <span class="keyword">this</span>.value = value;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">SimpleImmutableEntry</span><span class="params">(Entry&lt;? extends K, ? extends V&gt; entry)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.key   = entry.getKey();</span><br><span class="line">        <span class="keyword">this</span>.value = entry.getValue();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> K <span class="title">getKey</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> key;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> V <span class="title">getValue</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> value;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> V <span class="title">setValue</span><span class="params">(V value)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> UnsupportedOperationException();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object o)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> Map.Entry))</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        Map.Entry&lt;?,?&gt; e = (Map.Entry&lt;?,?&gt;)o;</span><br><span class="line">        <span class="keyword">return</span> eq(key, e.getKey()) &amp;&amp; eq(value, e.getValue());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> (key   == <span class="keyword">null</span> ? <span class="number">0</span> :   key.hashCode()) ^</span><br><span class="line">               (value == <span class="keyword">null</span> ? <span class="number">0</span> : value.hashCode());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> key + <span class="string">&quot;=&quot;</span> + value;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们通过阅读上述的源码不难发现，AbstractMap实现的操作都依赖于<code>entrySet()</code>所返回的集合视图。剩下的函数就没什么好说的了，有兴趣的话可以自己去看看。</p>
<h3 id="TreeMap"><a href="#TreeMap" class="headerlink" title="TreeMap"></a>TreeMap</h3><hr>
<p>TreeMap是基于红黑树（一种自平衡的二叉查找树）实现的一个保证有序性的Map，在继承关系结构图中可以得知TreeMap实现了NavigableMap接口，而该接口又继承了SortedMap接口，我们先来看看这两个接口定义了一些什么功能。</p>
<h4 id="SortedMap"><a href="#SortedMap" class="headerlink" title="SortedMap"></a>SortedMap</h4><hr>
<p>首先是SortedMap接口，实现该接口的实现类应当按照自然排序保证key的有序性，所谓自然排序即是根据key的<code>compareTo()</code>函数（需要实现Comparable接口）或者在构造函数中传入的Comparator实现类来进行排序，集合视图遍历元素的顺序也应当与key的顺序一致。SortedMap接口还定义了以下几个有效利用有序性的函数：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> java.util;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">SortedMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">Map</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 用于在此Map中对key进行排序的比较器，如果为null，则使用key的compareTo()函数进行比较。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Comparator&lt;? <span class="keyword">super</span> K&gt; comparator();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个key的范围为从fromKey到toKey的局部视图（包括fromKey，不包括toKey，包左不包右），</span></span><br><span class="line"><span class="comment">     * 如果fromKey和toKey是相等的，则返回一个空视图。</span></span><br><span class="line"><span class="comment">     * 返回的局部视图同样是此Map的集合视图，所以对它的操作是会与Map互相影响的。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">SortedMap&lt;K,V&gt; <span class="title">subMap</span><span class="params">(K fromKey, K toKey)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个严格地小于toKey的局部视图。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">SortedMap&lt;K,V&gt; <span class="title">headMap</span><span class="params">(K toKey)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个大于或等于fromKey的局部视图。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">SortedMap&lt;K,V&gt; <span class="title">tailMap</span><span class="params">(K fromKey)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回当前Map中的第一个key（最小）。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">K <span class="title">firstKey</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回当前Map中的最后一个key（最大）。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">K <span class="title">lastKey</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">Set&lt;K&gt; <span class="title">keySet</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">Collection&lt;V&gt; <span class="title">values</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    Set&lt;Map.Entry&lt;K, V&gt;&gt; entrySet();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="NavigableMap"><a href="#NavigableMap" class="headerlink" title="NavigableMap"></a>NavigableMap</h4><hr>
<p>然后是SortedMap的子接口NavigableMap，该接口扩展了一些用于导航（Navigation）的方法，像函数<code>lowerEntry(key)</code>会根据传入的参数key返回一个小于key的最大的一对键值对，例如，我们如下调用<code>lowerEntry(6)</code>，那么将返回key为5的键值对，如果没有key为5，则会返回key为4的键值对，以此类推，直到返回null（实在找不到的情况下）。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">    NavigableMap&lt;Integer, Integer&gt; map = <span class="keyword">new</span> TreeMap&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++)</span><br><span class="line">        map.put(i, i);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">assert</span> map.lowerEntry(<span class="number">6</span>).getKey() == <span class="number">5</span>;</span><br><span class="line">    <span class="keyword">assert</span> map.lowerEntry(<span class="number">5</span>).getKey() == <span class="number">4</span>;</span><br><span class="line">    <span class="keyword">assert</span> map.lowerEntry(<span class="number">0</span>).getKey() == <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>NavigableMap定义的都是一些类似于<code>lowerEntry(key)</code>的方法和以逆序、升序排序的集合视图，这些方法利用有序性实现了相比SortedMap接口更加灵活的操作。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> java.util;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">NavigableMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">SortedMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个小于指定key的最大的一对键值对，如果找不到则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Map.<span class="function">Entry&lt;K,V&gt; <span class="title">lowerEntry</span><span class="params">(K key)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个小于指定key的最大的一个key，如果找不到则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">K <span class="title">lowerKey</span><span class="params">(K key)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个小于或等于指定key的最大的一对键值对，如果找不到则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Map.<span class="function">Entry&lt;K,V&gt; <span class="title">floorEntry</span><span class="params">(K key)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个小于或等于指定key的最大的一个key，如果找不到则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">K <span class="title">floorKey</span><span class="params">(K key)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个大于或等于指定key的最小的一对键值对，如果找不到则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Map.<span class="function">Entry&lt;K,V&gt; <span class="title">ceilingEntry</span><span class="params">(K key)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个大于或等于指定key的最小的一个key，如果找不到则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">K <span class="title">ceilingKey</span><span class="params">(K key)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个大于指定key的最小的一对键值对，如果找不到则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Map.<span class="function">Entry&lt;K,V&gt; <span class="title">higherEntry</span><span class="params">(K key)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个大于指定key的最小的一个key，如果找不到则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">K <span class="title">higherKey</span><span class="params">(K key)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回该Map中最小的键值对，如果Map为空则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Map.<span class="function">Entry&lt;K,V&gt; <span class="title">firstEntry</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回该Map中最大的键值对，如果Map为空则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Map.<span class="function">Entry&lt;K,V&gt; <span class="title">lastEntry</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回并删除该Map中最小的键值对，如果Map为空则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Map.<span class="function">Entry&lt;K,V&gt; <span class="title">pollFirstEntry</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回并删除该Map中最大的键值对，如果Map为空则返回null。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Map.<span class="function">Entry&lt;K,V&gt; <span class="title">pollLastEntry</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个以当前Map降序（逆序）排序的集合视图</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">NavigableMap&lt;K,V&gt; <span class="title">descendingMap</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个包含当前Map中所有key的集合视图，该视图中的key以升序（正序）排序。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">NavigableSet&lt;K&gt; <span class="title">navigableKeySet</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个包含当前Map中所有key的集合视图，该视图中的key以降序（逆序）排序。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">NavigableSet&lt;K&gt; <span class="title">descendingKeySet</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 与SortedMap.subMap基本一致，区别在于多的两个参数fromInclusive和toInclusive，</span></span><br><span class="line"><span class="comment">     * 它们代表是否包含from和to，如果fromKey与toKey相等，并且fromInclusive与toInclusive</span></span><br><span class="line"><span class="comment">     * 都为true，那么不会返回空集合。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">NavigableMap&lt;K,V&gt; <span class="title">subMap</span><span class="params">(K fromKey, <span class="keyword">boolean</span> fromInclusive,</span></span></span><br><span class="line"><span class="function"><span class="params">                             K toKey,   <span class="keyword">boolean</span> toInclusive)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个小于或等于（inclusive为true的情况下）toKey的局部视图。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">NavigableMap&lt;K,V&gt; <span class="title">headMap</span><span class="params">(K toKey, <span class="keyword">boolean</span> inclusive)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回一个大于或等于（inclusive为true的情况下）fromKey的局部视图。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">NavigableMap&lt;K,V&gt; <span class="title">tailMap</span><span class="params">(K fromKey, <span class="keyword">boolean</span> inclusive)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 等价于subMap(fromKey, true, toKey, false)。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">SortedMap&lt;K,V&gt; <span class="title">subMap</span><span class="params">(K fromKey, K toKey)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 等价于headMap(toKey, false)。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">SortedMap&lt;K,V&gt; <span class="title">headMap</span><span class="params">(K toKey)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 等价于tailMap(fromKey, true)。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">SortedMap&lt;K,V&gt; <span class="title">tailMap</span><span class="params">(K fromKey)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>NavigableMap接口相对于SortedMap接口来说灵活了许多，正因为TreeMap也实现了该接口，所以在需要数据有序而且想灵活地访问它们的时候，使用TreeMap就非常合适了。</p>
<h4 id="红黑树"><a href="#红黑树" class="headerlink" title="红黑树"></a>红黑树</h4><hr>
<p>上文我们提到TreeMap的内部实现基于红黑树，而红黑树又是二叉查找树的一种。二叉查找树是一种有序的树形结构，优势在于查找、插入的时间复杂度只有<code>O(log n)</code>，特性如下：</p>
<ul>
<li><p>任意节点最多含有两个子节点。</p>
</li>
<li><p>任意节点的左、右节点都可以看做为一棵二叉查找树。</p>
</li>
<li><p>如果任意节点的左子树不为空，那么左子树上的所有节点的值均小于它的根节点的值。</p>
</li>
<li><p>如果任意节点的右子树不为空，那么右子树上的所有节点的值均大于它的根节点的值。</p>
</li>
<li><p>任意节点的key都是不同的。</p>
</li>
</ul>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/d/da/Binary_search_tree.svg" alt="二叉查找树"></p>
<p>尽管二叉查找树看起来很美好，但事与愿违，二叉查找树在极端情况下会变得并不是那么有效率，假设我们有一个有序的整数序列：<code>1,2,3,4,5,6,7,8,9,10,...</code>，如果把这个序列按顺序全部插入到二叉查找树时会发生什么呢？二叉查找树会产生倾斜，序列中的每一个元素都大于它的根节点（前一个元素），左子树永远是空的，那么这棵二叉查找树就跟一个普通的链表没什么区别了，查找操作的时间复杂度只有<code>O(n)</code>。</p>
<p>为了解决这个问题需要引入自平衡的二叉查找树，所谓自平衡，即是在树结构将要倾斜的情况下进行修正，这个修正操作被称为旋转，通过旋转操作可以让树趋于平衡。</p>
<p>红黑树是平衡二叉查找树的一种实现，它的名字来自于它的子节点是着色的，每个子节点非黑即红，由于只有两种颜色（两种状态），一般使用boolean来表示，下面为TreeMap中实现的Entry，它代表红黑树中的一个节点：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Red-black mechanics</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">boolean</span> RED   = <span class="keyword">false</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">boolean</span> BLACK = <span class="keyword">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Node in the Tree.  Doubles as a means to pass key-value pairs back to</span></span><br><span class="line"><span class="comment"> * user (see Map.Entry).</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">implements</span> <span class="title">Map</span>.<span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    K key;</span><br><span class="line">    V value;</span><br><span class="line">    Entry&lt;K,V&gt; left;</span><br><span class="line">    Entry&lt;K,V&gt; right;</span><br><span class="line">    Entry&lt;K,V&gt; parent;</span><br><span class="line">    <span class="keyword">boolean</span> color = BLACK;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Make a new cell with given key, value, and parent, and with</span></span><br><span class="line"><span class="comment">     * &#123;<span class="doctag">@code</span> null&#125; child links, and BLACK color.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Entry(K key, V value, Entry&lt;K,V&gt; parent) &#123;</span><br><span class="line">        <span class="keyword">this</span>.key = key;</span><br><span class="line">        <span class="keyword">this</span>.value = value;</span><br><span class="line">        <span class="keyword">this</span>.parent = parent;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns the key.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> the key</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> K <span class="title">getKey</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> key;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns the value associated with the key.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> the value associated with the key</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> V <span class="title">getValue</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> value;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Replaces the value currently associated with the key with the given</span></span><br><span class="line"><span class="comment">     * value.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> the value associated with the key before this method was</span></span><br><span class="line"><span class="comment">     *         called</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> V <span class="title">setValue</span><span class="params">(V value)</span> </span>&#123;</span><br><span class="line">        V oldValue = <span class="keyword">this</span>.value;</span><br><span class="line">        <span class="keyword">this</span>.value = value;</span><br><span class="line">        <span class="keyword">return</span> oldValue;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object o)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> Map.Entry))</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        Map.Entry&lt;?,?&gt; e = (Map.Entry&lt;?,?&gt;)o;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> valEquals(key,e.getKey()) &amp;&amp; valEquals(value,e.getValue());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> keyHash = (key==<span class="keyword">null</span> ? <span class="number">0</span> : key.hashCode());</span><br><span class="line">        <span class="keyword">int</span> valueHash = (value==<span class="keyword">null</span> ? <span class="number">0</span> : value.hashCode());</span><br><span class="line">        <span class="keyword">return</span> keyHash ^ valueHash;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> key + <span class="string">&quot;=&quot;</span> + value;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>任何平衡二叉查找树的查找操作都是与二叉查找树是一样的，因为查找操作并不会影响树的结构，也就不需要进行修正，代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">    Entry&lt;K,V&gt; p = getEntry(key);</span><br><span class="line">    <span class="keyword">return</span> (p==<span class="keyword">null</span> ? <span class="keyword">null</span> : p.value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">final</span> Entry&lt;K,V&gt; <span class="title">getEntry</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 使用Comparator进行比较</span></span><br><span class="line">    <span class="keyword">if</span> (comparator != <span class="keyword">null</span>)</span><br><span class="line">        <span class="keyword">return</span> getEntryUsingComparator(key);</span><br><span class="line">    <span class="keyword">if</span> (key == <span class="keyword">null</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">        Comparable&lt;? <span class="keyword">super</span> K&gt; k = (Comparable&lt;? <span class="keyword">super</span> K&gt;) key;</span><br><span class="line">    Entry&lt;K,V&gt; p = root;</span><br><span class="line">    <span class="comment">// 从根节点开始，不断比较key的大小进行查找</span></span><br><span class="line">    <span class="keyword">while</span> (p != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">int</span> cmp = k.compareTo(p.key);</span><br><span class="line">        <span class="keyword">if</span> (cmp &lt; <span class="number">0</span>) <span class="comment">// 小于，转向左子树</span></span><br><span class="line">            p = p.left;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (cmp &gt; <span class="number">0</span>) <span class="comment">// 大于，转向右子树</span></span><br><span class="line">            p = p.right;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="keyword">return</span> p;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>; <span class="comment">// 没有相等的key，返回null</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>而插入和删除操作与平衡二叉查找树的细节是息息相关的，关于红黑树的实现细节，我之前写过的一篇博客<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/2017/06/16/2017-06-16-RedBlackTree/">红黑树的那点事儿</a>已经讲的很清楚了，对这方面不了解的读者建议去阅读一下，就不在这里重复叙述了。</p>
<h4 id="集合视图"><a href="#集合视图" class="headerlink" title="集合视图"></a>集合视图</h4><hr>
<p>最后看一下TreeMap的集合视图的实现，集合视图一般都是实现了一个封装了当前实例的类，所以对集合视图的修改本质上就是在修改当前实例，TreeMap也不例外。</p>
<p>TreeMap的<code>headMap()</code>、<code>tailMap()</code>以及<code>subMap()</code>函数都返回了一个静态内部类AscendingSubMap&lt;K, V&gt;，从名字上也能猜出来，为了支持倒序，肯定也还有一个DescendingSubMap&lt;K, V&gt;，它们都继承于NavigableSubMap&lt;K, V&gt;，一个继承AbstractMap&lt;K, V&gt;并实现了NavigableMap&lt;K, V&gt;的抽象类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br></pre></td><td class="code"><pre><span class="line">  <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">NavigableSubMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">AbstractMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;</span></span><br><span class="line"><span class="class">      <span class="keyword">implements</span> <span class="title">NavigableMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;, <span class="title">java</span>.<span class="title">io</span>.<span class="title">Serializable</span> </span>&#123;</span><br><span class="line">      <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = -<span class="number">2102997345730753016L</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">final</span> TreeMap&lt;K,V&gt; m;</span><br><span class="line"></span><br><span class="line">      <span class="comment">/**</span></span><br><span class="line"><span class="comment">       * (fromStart, lo, loInclusive) 与 (toEnd, hi, hiInclusive)代表了两个三元组，</span></span><br><span class="line"><span class="comment">       * 如果fromStart为true，那么范围的下限（绝对）为map（被封装的TreeMap）的起始key，</span></span><br><span class="line"><span class="comment">       * 其他值将被忽略。</span></span><br><span class="line"><span class="comment">       * 如果loInclusive为true，lo将会被包含在范围内，否则lo是在范围外的。</span></span><br><span class="line"><span class="comment">       * toEnd与hiInclusive与上述逻辑相似，只不过考虑的是上限。</span></span><br><span class="line"><span class="comment">       */</span></span><br><span class="line">      <span class="keyword">final</span> K lo, hi;</span><br><span class="line">      <span class="keyword">final</span> <span class="keyword">boolean</span> fromStart, toEnd;</span><br><span class="line">      <span class="keyword">final</span> <span class="keyword">boolean</span> loInclusive, hiInclusive;</span><br><span class="line"></span><br><span class="line">      NavigableSubMap(TreeMap&lt;K,V&gt; m,</span><br><span class="line">                      <span class="keyword">boolean</span> fromStart, K lo, <span class="keyword">boolean</span> loInclusive,</span><br><span class="line">                      <span class="keyword">boolean</span> toEnd,     K hi, <span class="keyword">boolean</span> hiInclusive) &#123;</span><br><span class="line">          <span class="keyword">if</span> (!fromStart &amp;&amp; !toEnd) &#123;</span><br><span class="line">              <span class="keyword">if</span> (m.compare(lo, hi) &gt; <span class="number">0</span>)</span><br><span class="line">                  <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">&quot;fromKey &gt; toKey&quot;</span>);</span><br><span class="line">          &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">              <span class="keyword">if</span> (!fromStart) <span class="comment">// type check</span></span><br><span class="line">                  m.compare(lo, lo);</span><br><span class="line">              <span class="keyword">if</span> (!toEnd)</span><br><span class="line">                  m.compare(hi, hi);</span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          <span class="keyword">this</span>.m = m;</span><br><span class="line">          <span class="keyword">this</span>.fromStart = fromStart;</span><br><span class="line">          <span class="keyword">this</span>.lo = lo;</span><br><span class="line">          <span class="keyword">this</span>.loInclusive = loInclusive;</span><br><span class="line">          <span class="keyword">this</span>.toEnd = toEnd;</span><br><span class="line">          <span class="keyword">this</span>.hi = hi;</span><br><span class="line">          <span class="keyword">this</span>.hiInclusive = hiInclusive;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// internal utilities</span></span><br><span class="line"></span><br><span class="line">      <span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tooLow</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">if</span> (!fromStart) &#123;</span><br><span class="line">              <span class="keyword">int</span> c = m.compare(key, lo);</span><br><span class="line">              <span class="comment">// 如果key小于lo，或等于lo（需要lo不包含在范围内）</span></span><br><span class="line">              <span class="keyword">if</span> (c &lt; <span class="number">0</span> || (c == <span class="number">0</span> &amp;&amp; !loInclusive))</span><br><span class="line">                  <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tooHigh</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">if</span> (!toEnd) &#123;</span><br><span class="line">              <span class="keyword">int</span> c = m.compare(key, hi);</span><br><span class="line">              <span class="comment">// 如果key大于hi，或等于hi（需要hi不包含在范围内）</span></span><br><span class="line">              <span class="keyword">if</span> (c &gt; <span class="number">0</span> || (c == <span class="number">0</span> &amp;&amp; !hiInclusive))</span><br><span class="line">                  <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">inRange</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">return</span> !tooLow(key) &amp;&amp; !tooHigh(key);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">inClosedRange</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">return</span> (fromStart || m.compare(key, lo) &gt;= <span class="number">0</span>)</span><br><span class="line">              &amp;&amp; (toEnd || m.compare(hi, key) &gt;= <span class="number">0</span>);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 判断key是否在该视图的范围之内</span></span><br><span class="line">      <span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">inRange</span><span class="params">(Object key, <span class="keyword">boolean</span> inclusive)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">return</span> inclusive ? inRange(key) : inClosedRange(key);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="comment">/*</span></span><br><span class="line"><span class="comment">       * 以abs开头的函数为关系操作的绝对版本。</span></span><br><span class="line"><span class="comment">       */</span></span><br><span class="line"></span><br><span class="line">      <span class="comment">/*</span></span><br><span class="line"><span class="comment">       * 获得最小的键值对：</span></span><br><span class="line"><span class="comment">       * 如果fromStart为true，那么直接返回当前map实例的第一个键值对即可，</span></span><br><span class="line"><span class="comment">       * 否则，先判断lo是否包含在范围内，</span></span><br><span class="line"><span class="comment">       * 如果是，则获得当前map实例中大于或等于lo的最小的键值对，</span></span><br><span class="line"><span class="comment">       * 如果不是，则获得当前map实例中大于lo的最小的键值对。</span></span><br><span class="line"><span class="comment">       * 如果得到的结果e超过了范围的上限，那么返回null。</span></span><br><span class="line"><span class="comment">       */</span></span><br><span class="line">      <span class="keyword">final</span> TreeMap.<span class="function">Entry&lt;K,V&gt; <span class="title">absLowest</span><span class="params">()</span> </span>&#123;</span><br><span class="line">          TreeMap.Entry&lt;K,V&gt; e =</span><br><span class="line">              (fromStart ?  m.getFirstEntry() :</span><br><span class="line">               (loInclusive ? m.getCeilingEntry(lo) :</span><br><span class="line">                              m.getHigherEntry(lo)));</span><br><span class="line">          <span class="keyword">return</span> (e == <span class="keyword">null</span> || tooHigh(e.key)) ? <span class="keyword">null</span> : e;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 与absLowest()相反</span></span><br><span class="line">      <span class="keyword">final</span> TreeMap.<span class="function">Entry&lt;K,V&gt; <span class="title">absHighest</span><span class="params">()</span> </span>&#123;</span><br><span class="line">          TreeMap.Entry&lt;K,V&gt; e =</span><br><span class="line">              (toEnd ?  m.getLastEntry() :</span><br><span class="line">               (hiInclusive ?  m.getFloorEntry(hi) :</span><br><span class="line">                               m.getLowerEntry(hi)));</span><br><span class="line">          <span class="keyword">return</span> (e == <span class="keyword">null</span> || tooLow(e.key)) ? <span class="keyword">null</span> : e;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 下面的逻辑就都很简单了，注意会先判断key是否越界，</span></span><br><span class="line">      <span class="comment">// 如果越界就返回绝对值。</span></span><br><span class="line"></span><br><span class="line">      <span class="keyword">final</span> TreeMap.<span class="function">Entry&lt;K,V&gt; <span class="title">absCeiling</span><span class="params">(K key)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">if</span> (tooLow(key))</span><br><span class="line">              <span class="keyword">return</span> absLowest();</span><br><span class="line">          TreeMap.Entry&lt;K,V&gt; e = m.getCeilingEntry(key);</span><br><span class="line">          <span class="keyword">return</span> (e == <span class="keyword">null</span> || tooHigh(e.key)) ? <span class="keyword">null</span> : e;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">final</span> TreeMap.<span class="function">Entry&lt;K,V&gt; <span class="title">absHigher</span><span class="params">(K key)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">if</span> (tooLow(key)) </span><br><span class="line">              <span class="keyword">return</span> absLowest();</span><br><span class="line">          TreeMap.Entry&lt;K,V&gt; e = m.getHigherEntry(key);</span><br><span class="line">          <span class="keyword">return</span> (e == <span class="keyword">null</span> || tooHigh(e.key)) ? <span class="keyword">null</span> : e;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">final</span> TreeMap.<span class="function">Entry&lt;K,V&gt; <span class="title">absFloor</span><span class="params">(K key)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">if</span> (tooHigh(key))</span><br><span class="line">              <span class="keyword">return</span> absHighest();</span><br><span class="line">          TreeMap.Entry&lt;K,V&gt; e = m.getFloorEntry(key);</span><br><span class="line">          <span class="keyword">return</span> (e == <span class="keyword">null</span> || tooLow(e.key)) ? <span class="keyword">null</span> : e;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">final</span> TreeMap.<span class="function">Entry&lt;K,V&gt; <span class="title">absLower</span><span class="params">(K key)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">if</span> (tooHigh(key))</span><br><span class="line">              <span class="keyword">return</span> absHighest();</span><br><span class="line">          TreeMap.Entry&lt;K,V&gt; e = m.getLowerEntry(key);</span><br><span class="line">          <span class="keyword">return</span> (e == <span class="keyword">null</span> || tooLow(e.key)) ? <span class="keyword">null</span> : e;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="comment">/** 返回升序遍历的绝对上限 */</span></span><br><span class="line">      <span class="keyword">final</span> TreeMap.<span class="function">Entry&lt;K,V&gt; <span class="title">absHighFence</span><span class="params">()</span> </span>&#123;</span><br><span class="line">          <span class="keyword">return</span> (toEnd ? <span class="keyword">null</span> : (hiInclusive ?</span><br><span class="line">                                  m.getHigherEntry(hi) :</span><br><span class="line">                                  m.getCeilingEntry(hi)));</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="comment">/** 返回降序遍历的绝对下限 */</span></span><br><span class="line">      <span class="keyword">final</span> TreeMap.<span class="function">Entry&lt;K,V&gt; <span class="title">absLowFence</span><span class="params">()</span> </span>&#123;</span><br><span class="line">          <span class="keyword">return</span> (fromStart ? <span class="keyword">null</span> : (loInclusive ?</span><br><span class="line">                                      m.getLowerEntry(lo) :</span><br><span class="line">                                      m.getFloorEntry(lo)));</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 剩下的就是实现NavigableMap的方法以及一些抽象方法</span></span><br><span class="line"><span class="comment">// 和NavigableSubMap中的集合视图函数。</span></span><br><span class="line">      <span class="comment">// 大部分操作都是靠当前实例map的方法和上述用于判断边界的方法提供支持</span></span><br><span class="line">      .....</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>
<p>一个局部视图最重要的是要能够判断出传入的key是否属于该视图的范围内，在上面的代码中可以发现NavigableSubMap提供了非常多的辅助函数用于判断范围，接下来我们看看NavigableSubMap的迭代器是如何实现的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Iterators for SubMaps</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">SubMapIterator</span>&lt;<span class="title">T</span>&gt; <span class="keyword">implements</span> <span class="title">Iterator</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">    TreeMap.Entry&lt;K,V&gt; lastReturned;</span><br><span class="line">    TreeMap.Entry&lt;K,V&gt; next;</span><br><span class="line">    <span class="keyword">final</span> Object fenceKey;</span><br><span class="line">    <span class="keyword">int</span> expectedModCount;</span><br><span class="line"></span><br><span class="line">    SubMapIterator(TreeMap.Entry&lt;K,V&gt; first,</span><br><span class="line">                   TreeMap.Entry&lt;K,V&gt; fence) &#123;</span><br><span class="line">        expectedModCount = m.modCount; </span><br><span class="line">        lastReturned = <span class="keyword">null</span>;</span><br><span class="line">        next = first;</span><br><span class="line">        <span class="comment">// UNBOUNDED是一个虚拟值（一个Object对象），表示无边界。</span></span><br><span class="line">        fenceKey = fence == <span class="keyword">null</span> ? UNBOUNDED : fence.key;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 只要next不为null并且没有超过边界</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">hasNext</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> next != <span class="keyword">null</span> &amp;&amp; next.key != fenceKey;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> TreeMap.<span class="function">Entry&lt;K,V&gt; <span class="title">nextEntry</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        TreeMap.Entry&lt;K,V&gt; e = next;</span><br><span class="line">        <span class="comment">// 已经遍历到头或者越界了</span></span><br><span class="line">        <span class="keyword">if</span> (e == <span class="keyword">null</span> || e.key == fenceKey)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> NoSuchElementException();</span><br><span class="line">        <span class="comment">// modCount是一个记录操作数的计数器</span></span><br><span class="line">        <span class="comment">// 如果与expectedModCount不一致</span></span><br><span class="line">        <span class="comment">// 则代表当前map实例在遍历过程中已被修改过了（从其他线程）</span></span><br><span class="line">        <span class="keyword">if</span> (m.modCount != expectedModCount)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException();</span><br><span class="line">        <span class="comment">// 向后移动next指针</span></span><br><span class="line">        <span class="comment">// successor()返回指定节点的继任者</span></span><br><span class="line">        <span class="comment">// 它是节点e的右子树的最左节点</span></span><br><span class="line">        <span class="comment">// 也就是比e大的最小的节点</span></span><br><span class="line">        <span class="comment">// 如果e没有右子树，则会试图向上寻找</span></span><br><span class="line">        next = successor(e);</span><br><span class="line">        lastReturned = e; <span class="comment">// 记录最后返回的节点</span></span><br><span class="line">        <span class="keyword">return</span> e;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> TreeMap.<span class="function">Entry&lt;K,V&gt; <span class="title">prevEntry</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        TreeMap.Entry&lt;K,V&gt; e = next;</span><br><span class="line">        <span class="keyword">if</span> (e == <span class="keyword">null</span> || e.key == fenceKey)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> NoSuchElementException();</span><br><span class="line">        <span class="keyword">if</span> (m.modCount != expectedModCount)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException();</span><br><span class="line">        <span class="comment">// 向前移动next指针</span></span><br><span class="line">        <span class="comment">// predecessor()返回指定节点的前任</span></span><br><span class="line">        <span class="comment">// 它与successor()逻辑相反。</span></span><br><span class="line">        next = predecessor(e);</span><br><span class="line">        lastReturned = e;</span><br><span class="line">        <span class="keyword">return</span> e;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">removeAscending</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (lastReturned == <span class="keyword">null</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException();</span><br><span class="line">        <span class="keyword">if</span> (m.modCount != expectedModCount)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException();</span><br><span class="line">        <span class="comment">// 被删除的节点被它的继任者取代</span></span><br><span class="line">        <span class="comment">// 执行完删除后，lastReturned实际指向了它的继任者</span></span><br><span class="line">        <span class="keyword">if</span> (lastReturned.left != <span class="keyword">null</span> &amp;&amp; lastReturned.right != <span class="keyword">null</span>)</span><br><span class="line">            next = lastReturned;</span><br><span class="line">        m.deleteEntry(lastReturned);</span><br><span class="line">        lastReturned = <span class="keyword">null</span>;</span><br><span class="line">        expectedModCount = m.modCount;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">removeDescending</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (lastReturned == <span class="keyword">null</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException();</span><br><span class="line">        <span class="keyword">if</span> (m.modCount != expectedModCount)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException();</span><br><span class="line">        m.deleteEntry(lastReturned);</span><br><span class="line">        lastReturned = <span class="keyword">null</span>;</span><br><span class="line">        expectedModCount = m.modCount;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">SubMapEntryIterator</span> <span class="keyword">extends</span> <span class="title">SubMapIterator</span>&lt;<span class="title">Map</span>.<span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;&gt; </span>&#123;</span><br><span class="line">    SubMapEntryIterator(TreeMap.Entry&lt;K,V&gt; first,</span><br><span class="line">                        TreeMap.Entry&lt;K,V&gt; fence) &#123;</span><br><span class="line">        <span class="keyword">super</span>(first, fence);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> Map.<span class="function">Entry&lt;K,V&gt; <span class="title">next</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> nextEntry();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        removeAscending();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">DescendingSubMapEntryIterator</span> <span class="keyword">extends</span> <span class="title">SubMapIterator</span>&lt;<span class="title">Map</span>.<span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;&gt; </span>&#123;</span><br><span class="line">    DescendingSubMapEntryIterator(TreeMap.Entry&lt;K,V&gt; last,</span><br><span class="line">                                  TreeMap.Entry&lt;K,V&gt; fence) &#123;</span><br><span class="line">        <span class="keyword">super</span>(last, fence);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Map.<span class="function">Entry&lt;K,V&gt; <span class="title">next</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> prevEntry();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        removeDescending();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>到目前为止，我们已经针对集合视图讨论了许多，想必大家也能够理解集合视图的概念了，由于SortedMap与NavigableMap的缘故，TreeMap中的集合视图是非常多的，包括各种局部视图和不同排序的视图，有兴趣的读者可以自己去看看源码，后面的内容不会再对集合视图进行过多的解释了。</p>
<h3 id="HashMap"><a href="#HashMap" class="headerlink" title="HashMap"></a>HashMap</h3><hr>
<p>光从名字上应该也能猜到，HashMap肯定是基于hash算法实现的，这种基于hash实现的map叫做散列表（hash table）。</p>
<p>散列表中维护了一个数组，数组的每一个元素被称为一个桶（bucket），当你传入一个<code>key = &quot;a&quot;</code>进行查询时，散列表会先把key传入散列（hash）函数中进行寻址，得到的结果就是数组的下标，然后再通过这个下标访问数组即可得到相关联的值。</p>
<p><img src="http://wx1.sinaimg.cn/large/63503acbly1fpu3a5d3p7j20j00bpwex.jpg"></p>
<p>我们都知道数组中数据的组织方式是线性的，它会直接分配一串连续的内存地址序列，要找到一个元素只需要根据下标来计算地址的偏移量即可（查找一个元素的起始地址为：数组的起始地址加上下标乘以该元素类型占用的地址大小）。因此散列表在理想的情况下，各种操作的时间复杂度只有<code>O(1)</code>，这甚至超过了二叉查找树，虽然理想的情况并不总是满足的，关于这点之后我们还会提及。</p>
<h4 id="为什么是hash？"><a href="#为什么是hash？" class="headerlink" title="为什么是hash？"></a>为什么是hash？</h4><hr>
<p>hash算法是一种可以从任何数据中提取出其“指纹”的数据摘要算法，它将任意大小的数据（输入）映射到一个固定大小的序列（输出）上，这个序列被称为hash code、数据摘要或者指纹。比较出名的hash算法有MD5、SHA。</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/d/da/Hash_function.svg"></p>
<p>hash是具有唯一性且不可逆的，唯一性指的是相同的输入产生的hash code永远是一样的，而不可逆也比较容易理解，数据摘要算法并不是压缩算法，它只是生成了一个该数据的摘要，没有将数据进行压缩。压缩算法一般都是使用一种更节省空间的编码规则将数据重新编码，解压缩只需要按着编码规则解码就是了，试想一下，一个几百MB甚至几GB的数据生成的hash code都只是一个拥有固定长度的序列，如果再能逆向解压缩，那么其他压缩算法该情何以堪？</p>
<p>我们上述讨论的仅仅是在密码学中的hash算法，而在散列表中所需要的散列函数是要能够将key寻址到buckets中的一个位置，散列函数的实现影响到整个散列表的性能。</p>
<p>一个完美的散列函数要能够做到均匀地将key分布到buckets中，每一个key分配到一个bucket，但这是不可能的。虽然hash算法具有唯一性，但同时它还具有重复性，唯一性保证了相同输入的输出是一致的，却没有保证不同输入的输出是不一致的，也就是说，完全有可能两个不同的key被分配到了同一个bucket（因为它们的hash code可能是相同的），这叫做碰撞冲突。总之，理想很丰满，现实很骨感，散列函数只能尽可能地减少冲突，没有办法完全消除冲突。</p>
<p>散列函数的实现方法非常多，一个优秀的散列函数要看它能不能将key分布均匀。首先介绍一种最简单的方法：除留余数法，先对key进行hash得到它的hash code，然后再用该hash code对buckets数组的元素数量取余，得到的结果就是bucket的下标，这种方法简单高效，也可以当做对集群进行负载均衡的路由算法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">hash</span><span class="params">(Key key)</span> </span>&#123;</span><br><span class="line">   <span class="comment">// &amp; 0x7fffffff 是为了屏蔽符号位，M为bucket数组的长度</span></span><br><span class="line">   <span class="keyword">return</span> (key.hashCode() &amp; <span class="number">0x7fffffff</span>) % M;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>要注意一点，只有整数才能进行取余运算，如果hash code是一个字符串或别的类型，那么你需要将它转换为整数才能使用除留余数法，不过Java在Object对象中提供了<code>hashCode()</code>函数，该函数返回了一个int值，所以任何你想要放入HashMap的自定义的抽象数据类型，都必须实现该函数和<code>equals()</code>函数，这两个函数之间也遵守着一种约定：如果<code>a.equals(b) == true</code>，那么a与b的<code>hashCode()</code>也必须是相同的。</p>
<p>下面为String类的<code>hashCode()</code>函数，它先遍历了内部的字符数组，然后在每一次循环中计算hash code（将hash code乘以一个素数并加上当前循环项的字符）：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** The value is used for character storage. */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">char</span> value[];</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Cache the hash code for the string */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> hash; <span class="comment">// Default to 0</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> h = hash;</span><br><span class="line">    <span class="keyword">if</span> (h == <span class="number">0</span> &amp;&amp; value.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">char</span> val[] = value;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; value.length; i++) &#123;</span><br><span class="line">            h = <span class="number">31</span> * h + val[i];</span><br><span class="line">        &#125;</span><br><span class="line">        hash = h;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> h;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>HashMap没有采用这么简单的方法，有一个原因是HashMap中的buckets数组的长度永远为一个2的幂，而不是一个素数，如果长度为素数，那么可能会更适合简单暴力的除留余数法（当然除留余数法虽然简单却并不是那么高效的），顺便一提，时代的眼泪Hashtable就使用了除留余数法，它没有强制约束buckets数组的长度。</p>
<p>HashMap在内部实现了一个<code>hash()</code>函数，首先要对<code>hashCode()</code>的返回值进行处理：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">hash</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> h;</span><br><span class="line">    <span class="keyword">return</span> (key == <span class="keyword">null</span>) ? <span class="number">0</span> : (h = key.hashCode()) ^ (h &gt;&gt;&gt; <span class="number">16</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>该函数将<code>key.hashCode()</code>的低16位和高16位做了个异或运算，其目的是为了扰乱低位的信息以实现减少碰撞冲突。之后还需要把<code>hash()</code>的返回值与<code>table.length - 1</code>做与运算（<code>table</code>为buckets数组），得到的结果即是数组的下标。</p>
<p><code>table.length - 1</code>就像是一个低位掩码（这个设计也优化了扩容操作的性能），它和<code>hash()</code>做与操作时必然会将高位屏蔽（因为一个HashMap不可能有特别大的buckets数组，至少在不断自动扩容之前是不可能的，所以<code>table.length - 1</code>的大部分高位都为0），只保留低位，看似没什么毛病，但这其实暗藏玄机，它会导致总是只有最低的几位是有效的，这样就算你的<code>hashCode()</code>实现得再好也难以避免发生碰撞。这时，<code>hash()</code>函数的价值就体现出来了，它对hash code的低位添加了随机性并且混合了高位的部分特征，显著减少了碰撞冲突的发生（关于<code>hash()</code>函数的效果如何，可以参考这篇文章<a target="_blank" rel="noopener" href="http://vanillajava.blogspot.com/2015/09/an-introduction-to-optimising-hashing.html">An introduction to optimising a hashing strategy</a>）。</p>
<p>HashMap的散列函数具体流程如下图：</p>
<p><img src="http://wx2.sinaimg.cn/large/63503acbly1fpu3a5ojfoj20rx0dhdh4.jpg"></p>
<h4 id="解决冲突"><a href="#解决冲突" class="headerlink" title="解决冲突"></a>解决冲突</h4><hr>
<p>在上文中我们已经多次提到碰撞冲突，但是散列函数不可能是完美的，key分布完全均匀的情况是不存在的，所以碰撞冲突总是难以避免。</p>
<p>那么发生碰撞冲突时怎么办？总不能丢弃数据吧？必须要有一种合理的方法来解决这个问题，HashMap使用了叫做分离链接（Separate chaining，也有人翻译成拉链法）的策略来解决冲突。它的主要思想是每个bucket都应当是一个互相独立的数据结构，当发生冲突时，只需要把数据放入bucket中（因为bucket本身也是一个可以存放数据的数据结构），这样查询一个key所消耗的时间为访问bucket所消耗的时间加上在bucket中查找的时间。</p>
<p>HashMap的buckets数组其实就是一个链表数组，在发生冲突时只需要把Entry（还记得Entry吗？HashMap的Entry实现就是一个简单的链表节点，它包含了key和value以及hash code）放到链表的尾部，如果未发生冲突（位于该下标的bucket为null），那么就把该Entry做为链表的头部。而且HashMap还使用了Lazy策略，buckets数组只会在第一次调用<code>put()</code>函数时进行初始化，这是一种防止内存浪费的做法，像ArrayList也是Lazy的，它在第一次调用<code>add()</code>时才会初始化内部的数组。</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/d/d0/Hash_table_5_0_1_1_1_1_1_LL.svg" alt="分离链接法"></p>
<p>不过链表虽然实现简单，但是在查找的效率上只有<code>O(n)</code>，而且我们大部分的操作都是在进行查找，在<code>hashCode()</code>设计的不是非常良好的情况下，碰撞冲突可能会频繁发生，链表也会变得越来越长，这个效率是非常差的。Java 8对其实现了优化，链表的节点数量在到达阈值时会转化为红黑树，这样查找所需的时间就只有<code>O(log n)</code>了，阈值的定义如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The bin count threshold for using a tree rather than list for a</span></span><br><span class="line"><span class="comment"> * bin.  Bins are converted to trees when adding an element to a</span></span><br><span class="line"><span class="comment"> * bin with at least this many nodes. The value must be greater</span></span><br><span class="line"><span class="comment"> * than 2 and should be at least 8 to mesh with assumptions in</span></span><br><span class="line"><span class="comment"> * tree removal about conversion back to plain bins upon</span></span><br><span class="line"><span class="comment"> * shrinkage.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> TREEIFY_THRESHOLD = <span class="number">8</span>;</span><br></pre></td></tr></table></figure>
<p>如果在插入Entry时发现一条链表超过阈值，就会执行以下的操作，对该链表进行树化；相对的，如果在删除Entry（或进行扩容）时发现红黑树的节点太少（根据阈值UNTREEIFY_THRESHOLD），也会把红黑树退化成链表。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 替换指定hash所处位置的链表中的所有节点为TreeNode，</span></span><br><span class="line"><span class="comment"> * 如果buckets数组太小，就进行扩容。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">treeifyBin</span><span class="params">(Node&lt;K,V&gt;[] tab, <span class="keyword">int</span> hash)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> n, index; Node&lt;K,V&gt; e;</span><br><span class="line">    <span class="comment">// MIN_TREEIFY_CAPACITY = 64，小于该值代表数组中的节点并不是很多</span></span><br><span class="line">    <span class="comment">// 所以选择进行扩容，只有数组长度大于该值时才会进行树化。</span></span><br><span class="line">    <span class="keyword">if</span> (tab == <span class="keyword">null</span> || (n = tab.length) &lt; MIN_TREEIFY_CAPACITY)</span><br><span class="line">        resize();</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> ((e = tab[index = (n - <span class="number">1</span>) &amp; hash]) != <span class="keyword">null</span>) &#123;</span><br><span class="line">        TreeNode&lt;K,V&gt; hd = <span class="keyword">null</span>, tl = <span class="keyword">null</span>;</span><br><span class="line">        <span class="comment">// 转换链表节点为树节点，注意要处理好连接关系</span></span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            TreeNode&lt;K,V&gt; p = replacementTreeNode(e, <span class="keyword">null</span>);</span><br><span class="line">            <span class="keyword">if</span> (tl == <span class="keyword">null</span>)</span><br><span class="line">                hd = p;</span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                p.prev = tl;</span><br><span class="line">                tl.next = p;</span><br><span class="line">            &#125;</span><br><span class="line">            tl = p;</span><br><span class="line">        &#125; <span class="keyword">while</span> ((e = e.next) != <span class="keyword">null</span>);</span><br><span class="line">        <span class="keyword">if</span> ((tab[index] = hd) != <span class="keyword">null</span>)</span><br><span class="line">            hd.treeify(tab); <span class="comment">// 从头部开始构造树</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 该函数定义在TreeNode中</span></span><br><span class="line">    <span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">treeify</span><span class="params">(Node&lt;K,V&gt;[] tab)</span> </span>&#123;</span><br><span class="line">        TreeNode&lt;K,V&gt; root = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">for</span> (TreeNode&lt;K,V&gt; x = <span class="keyword">this</span>, next; x != <span class="keyword">null</span>; x = next) &#123;</span><br><span class="line">            next = (TreeNode&lt;K,V&gt;)x.next;</span><br><span class="line">            x.left = x.right = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">if</span> (root == <span class="keyword">null</span>) &#123; <span class="comment">// 初始化root节点</span></span><br><span class="line">                x.parent = <span class="keyword">null</span>;</span><br><span class="line">                x.red = <span class="keyword">false</span>;</span><br><span class="line">                root = x;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                K k = x.key;</span><br><span class="line">                <span class="keyword">int</span> h = x.hash;</span><br><span class="line">                Class&lt;?&gt; kc = <span class="keyword">null</span>;</span><br><span class="line">                <span class="keyword">for</span> (TreeNode&lt;K,V&gt; p = root;;) &#123;</span><br><span class="line">                    <span class="keyword">int</span> dir, ph;</span><br><span class="line">                    K pk = p.key;</span><br><span class="line">                    <span class="comment">// 确定节点的方向</span></span><br><span class="line">                    <span class="keyword">if</span> ((ph = p.hash) &gt; h)</span><br><span class="line">                        dir = -<span class="number">1</span>;</span><br><span class="line">                    <span class="keyword">else</span> <span class="keyword">if</span> (ph &lt; h)</span><br><span class="line">                        dir = <span class="number">1</span>;</span><br><span class="line">                    <span class="comment">// 如果kc == null</span></span><br><span class="line">                    <span class="comment">// 并且k没有实现Comparable接口</span></span><br><span class="line">                    <span class="comment">// 或者k与pk是没有可比较性的（类型不同）</span></span><br><span class="line">                    <span class="comment">// 或者k与pk是相等的（返回0也有可能是相等）</span></span><br><span class="line">                    <span class="keyword">else</span> <span class="keyword">if</span> ((kc == <span class="keyword">null</span> &amp;&amp;</span><br><span class="line">                              (kc = comparableClassFor(k)) == <span class="keyword">null</span>) ||</span><br><span class="line">                             (dir = compareComparables(kc, k, pk)) == <span class="number">0</span>)</span><br><span class="line">                        dir = tieBreakOrder(k, pk);</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 确定方向后插入节点，修正红黑树的平衡</span></span><br><span class="line">                    TreeNode&lt;K,V&gt; xp = p;</span><br><span class="line">                    <span class="keyword">if</span> ((p = (dir &lt;= <span class="number">0</span>) ? p.left : p.right) == <span class="keyword">null</span>) &#123;</span><br><span class="line">                        x.parent = xp;</span><br><span class="line">                        <span class="keyword">if</span> (dir &lt;= <span class="number">0</span>)</span><br><span class="line">                            xp.left = x;</span><br><span class="line">                        <span class="keyword">else</span></span><br><span class="line">                            xp.right = x;</span><br><span class="line">                        root = balanceInsertion(root, x);</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 确保给定的root是该bucket中的第一个节点</span></span><br><span class="line">        moveRootToFront(tab, root);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">tieBreakOrder</span><span class="params">(Object a, Object b)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> d;</span><br><span class="line">        <span class="keyword">if</span> (a == <span class="keyword">null</span> || b == <span class="keyword">null</span> ||</span><br><span class="line">            (d = a.getClass().getName().</span><br><span class="line">             compareTo(b.getClass().getName())) == <span class="number">0</span>)</span><br><span class="line">            <span class="comment">// System.identityHashCode()将调用并返回传入对象的默认hashCode()</span></span><br><span class="line">            <span class="comment">// 也就是说，无论是否重写了hashCode()，都将调用Object.hashCode()。</span></span><br><span class="line">            <span class="comment">// 如果传入的对象是null，那么就返回0</span></span><br><span class="line">            d = (System.identityHashCode(a) &lt;= System.identityHashCode(b) ?</span><br><span class="line">                 -<span class="number">1</span> : <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">return</span> d;</span><br><span class="line">    &#125;            </span><br></pre></td></tr></table></figure>
<p>解决碰撞冲突的另一种策略叫做开放寻址法（Open addressing），它与分离链接法的思想截然不同。在开放寻址法中，所有Entry都会存储在buckets数组，一个明显的区别是，分离链接法中的每个bucket都是一个链表或其他的数据结构，而开放寻址法中的每个bucket就仅仅只是Entry本身。</p>
<p>开放寻址法是基于数组中的空位来解决冲突的，它的想法很简单，与其使用链表等数据结构，不如直接在数组中留出空位来当做一个标记，反正都要占用额外的内存。</p>
<p>当你查找一个key的时候，首先会从起始位置（通过散列函数计算出的数组索引）开始，不断检查当前bucket是否为目标Entry（通过比较key来判断），如果当前bucket不是目标Entry，那么就向后查找（查找的间隔取决于实现），直到碰见一个空位（null），这代表你想要找的key不存在。</p>
<p>如果你想要put一个全新的Entry（Map中没有这个key存在），依然会从起始位置开始进行查找，如果起始位置不是空的，则代表发生了碰撞冲突，只好不断向后查找，直到发现一个空位。</p>
<p>开放寻址法的名字也是来源于此，一个Entry的位置并不是完全由hash值决定的，所以也叫做Closed hashing，相对的，分离链接法也被称为Open hashing或Closed addressing。</p>
<p>根据向后探测（查找）的算法不同，开放寻址法有多种不同的实现，我们介绍一种最简单的算法：线性探测法（Linear probing），在发生碰撞时，简单地将索引加一，如果到达了数组的尾部就折回到数组的头部，直到找到目标或一个空位。</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/9/90/HASHTB12.svg" alt="线性探测法"></p>
<p>基于线性探测法的查找操作如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> K[] keys; <span class="comment">// 存储key的数组</span></span><br><span class="line"><span class="keyword">private</span> V[] vals; <span class="comment">// 存储值的数组 </span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(K key)</span> </span>&#123;</span><br><span class="line">	<span class="comment">// m是buckets数组的长度，即keys和vals的长度。</span></span><br><span class="line">	<span class="comment">// 当i等于m时，取模运算会得0（折回数组头部）</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = hash(key); keys[i] != <span class="keyword">null</span>; i = (i + <span class="number">1</span>) % m) &#123;</span><br><span class="line">        <span class="keyword">if</span> (keys[i].equals(key))</span><br><span class="line">            <span class="keyword">return</span> vals[i];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>插入操作稍微麻烦一些，需要在插入之前判断当前数组的剩余容量，然后决定是否扩容。数组的剩余容量越多，代表Entry之间的间隔越大以及越早碰见空位（向后探测的次数就越少），效率自然就会变高。代价就是额外消耗的内存较多，这也是在用空间换取时间。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">put</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// n是Entry的数量，如果n超过了数组长度的一半，就扩容一倍</span></span><br><span class="line">    <span class="keyword">if</span> (n &gt;= m / <span class="number">2</span>) resize(<span class="number">2</span> * m);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> i;</span><br><span class="line">    <span class="keyword">for</span> (i = hash(key); keys[i] != <span class="keyword">null</span>; i = (i + <span class="number">1</span>) % m) &#123;</span><br><span class="line">        <span class="keyword">if</span> (keys[i].equals(key)) &#123;</span><br><span class="line">            vals[i] = value;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 没有找到目标，那么就插入一对新的Entry</span></span><br><span class="line">    keys[i] = key;</span><br><span class="line">    vals[i] = value;</span><br><span class="line">    n++;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>接下来是删除操作，需要注意一点，我们不能简单地把目标key所在的位置（keys和vals数组）设置为null，这样会导致此位置之后的Entry无法被探测到，所以需要将目标右侧的所有Entry重新插入到散列表中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">delete</span><span class="params">(K key)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> i = hash(key);</span><br><span class="line">    <span class="comment">// 先找到目标的索引</span></span><br><span class="line">    <span class="keyword">while</span> (!key.equals(keys[i])) &#123;</span><br><span class="line">        i = (i + <span class="number">1</span>) % m;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    V oldValue = vals[i];</span><br><span class="line">    <span class="comment">// 删除目标key和value</span></span><br><span class="line">    keys[i] = <span class="keyword">null</span>;</span><br><span class="line">    vals[i] = <span class="keyword">null</span>;</span><br><span class="line">    <span class="comment">// 指针移动到下一个索引</span></span><br><span class="line">    i = (i + <span class="number">1</span>) % m;</span><br><span class="line">    <span class="keyword">while</span> (keys[i] != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 先删除然后重新插入</span></span><br><span class="line">        K keyToRehash = keys[i];</span><br><span class="line">        V valToRehash = vals[i];</span><br><span class="line">        keys[i] = <span class="keyword">null</span>;</span><br><span class="line">        vals[i] = <span class="keyword">null</span>;</span><br><span class="line">        n--;</span><br><span class="line">        put(keyToRehash, valToRehash);</span><br><span class="line">        i = (i + <span class="number">1</span>) % m;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    n--;</span><br><span class="line">    <span class="comment">// 当前Entry小于等于数组长度的八分之一时，进行缩容</span></span><br><span class="line">    <span class="keyword">if</span> (n &gt; <span class="number">0</span> &amp;&amp; n &lt;= m / <span class="number">8</span>) resize(m / <span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> oldValue;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="动态扩容"><a href="#动态扩容" class="headerlink" title="动态扩容"></a>动态扩容</h4><hr>
<p>散列表以数组的形式组织bucket，问题在于数组是静态分配的，为了保证查找的性能，需要在Entry数量大于一个临界值时进行扩容，否则就算散列函数的效果再好，也难免产生碰撞。</p>
<p>所谓扩容，其实就是用一个容量更大（在原容量上乘以二）的数组来替换掉当前的数组，这个过程需要把旧数组中的数据重新hash到新数组，所以扩容也能在一定程度上减缓碰撞。</p>
<p>HashMap通过负载因子（Load Factor）乘以buckets数组的长度来计算出临界值，算法：<code>threshold = load_factor * capacity</code>。比如，HashMap的默认初始容量为16（<code>capacity = 16</code>），默认负载因子为0.75（<code>load_factor = 0.75</code>），那么临界值就为<code>threshold = 0.75 * 16 = 12</code>，只要Entry的数量大于12，就会触发扩容操作。</p>
<p>还可以通过下列的构造函数来自定义负载因子，负载因子越小查找的性能就会越高，但同时额外占用的内存就会越多，如果没有特殊需要不建议修改默认值。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 可以发现构造函数中根本就没初始化buckets数组。</span></span><br><span class="line"><span class="comment"> * （之前说过buckets数组会推迟到第一次调用put()时进行初始化）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity, <span class="keyword">float</span> loadFactor)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (initialCapacity &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">&quot;Illegal initial capacity: &quot;</span> +</span><br><span class="line">                                           initialCapacity);</span><br><span class="line">    <span class="keyword">if</span> (initialCapacity &gt; MAXIMUM_CAPACITY)</span><br><span class="line">        initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line">    <span class="keyword">if</span> (loadFactor &lt;= <span class="number">0</span> || Float.isNaN(loadFactor))</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">&quot;Illegal load factor: &quot;</span> +</span><br><span class="line">                                           loadFactor);</span><br><span class="line">    <span class="keyword">this</span>.loadFactor = loadFactor;</span><br><span class="line">    <span class="comment">// tableSizeFor()确保initialCapacity必须为一个2的N次方</span></span><br><span class="line">    <span class="keyword">this</span>.threshold = tableSizeFor(initialCapacity);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>buckets数组的大小约束对于整个HashMap都至关重要，为了防止传入一个不是2次幂的整数，必须要有所防范。<code>tableSizeFor()</code>函数会尝试修正一个整数，并转换为离该整数最近的2次幂。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns a power of two size for the given target capacity.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">tableSizeFor</span><span class="params">(<span class="keyword">int</span> cap)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> n = cap - <span class="number">1</span>;</span><br><span class="line">    n |= n &gt;&gt;&gt; <span class="number">1</span>;</span><br><span class="line">    n |= n &gt;&gt;&gt; <span class="number">2</span>;</span><br><span class="line">    n |= n &gt;&gt;&gt; <span class="number">4</span>;</span><br><span class="line">    n |= n &gt;&gt;&gt; <span class="number">8</span>;</span><br><span class="line">    n |= n &gt;&gt;&gt; <span class="number">16</span>;</span><br><span class="line">    <span class="keyword">return</span> (n &lt; <span class="number">0</span>) ? <span class="number">1</span> : (n &gt;= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><img src="http://wx1.sinaimg.cn/large/63503acbly1fpu3a6fobzj20pq0oy0th.jpg" alt="tableSizeFor()工作流程"></p>
<p>还记得数组索引的计算方法吗？<code>index = (table.length - 1) &amp; hash</code>，这其实是一种优化手段，由于数组的大小永远是一个2次幂，在扩容之后，一个元素的新索引要么是在原位置，要么就是在原位置加上扩容前的容量。这个方法的巧妙之处全在于&amp;运算，之前提到过&amp;运算只会关注n - 1（n = 数组长度）的有效位，当扩容之后，n的有效位相比之前会多增加一位（n会变成之前的二倍，所以确保数组长度永远是2次幂很重要），然后只需要判断hash在新增的有效位的位置是0还是1就可以算出新的索引位置，如果是0，那么索引没有发生变化，如果是1，索引就为原索引加上扩容前的容量。</p>
<p><img src="http://wx1.sinaimg.cn/large/63503acbly1fpu3a7n647j20z00fqq48.jpg" alt="扩容前与扩容后的索引计算"></p>
<p>这样在每次扩容时都不用重新计算hash，省去了不少时间，而且新增有效位是0还是1是带有随机性的，之前两个碰撞的Entry又有可能在扩容时再次均匀地散布开。下面是<code>resize()</code>的源码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> Node&lt;K,V&gt;[] resize() &#123;</span><br><span class="line">    Node&lt;K,V&gt;[] oldTab = table; <span class="comment">// table就是buckets数组</span></span><br><span class="line">    <span class="keyword">int</span> oldCap = (oldTab == <span class="keyword">null</span>) ? <span class="number">0</span> : oldTab.length;</span><br><span class="line">    <span class="keyword">int</span> oldThr = threshold;</span><br><span class="line">    <span class="keyword">int</span> newCap, newThr = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// oldCap大于0，进行扩容，设置阈值与新的容量</span></span><br><span class="line">    <span class="keyword">if</span> (oldCap &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 超过最大值不会进行扩容，并且把阈值设置成Interger.MAX_VALUE</span></span><br><span class="line">        <span class="keyword">if</span> (oldCap &gt;= MAXIMUM_CAPACITY) &#123;</span><br><span class="line">            threshold = Integer.MAX_VALUE;</span><br><span class="line">            <span class="keyword">return</span> oldTab;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 没超过最大值，扩容为原来的2倍</span></span><br><span class="line">        <span class="comment">// 向左移1位等价于乘2</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((newCap = oldCap &lt;&lt; <span class="number">1</span>) &lt; MAXIMUM_CAPACITY &amp;&amp;</span><br><span class="line">                 oldCap &gt;= DEFAULT_INITIAL_CAPACITY)</span><br><span class="line">            newThr = oldThr &lt;&lt; <span class="number">1</span>; <span class="comment">// double threshold</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// oldCap = 0，oldThr大于0，那么就把阈值做为新容量以进行初始化</span></span><br><span class="line">    <span class="comment">// 这种情况发生在用户调用了带有参数的构造函数（会对threshold进行初始化）</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (oldThr &gt; <span class="number">0</span>) <span class="comment">// initial capacity was placed in threshold</span></span><br><span class="line">        newCap = oldThr;</span><br><span class="line">    <span class="comment">// oldCap与oldThr都为0，这种情况发生在用户调用了无参构造函数</span></span><br><span class="line">    <span class="comment">// 采用默认值进行初始化</span></span><br><span class="line">    <span class="keyword">else</span> &#123;               <span class="comment">// zero initial threshold signifies using defaults</span></span><br><span class="line">        newCap = DEFAULT_INITIAL_CAPACITY;</span><br><span class="line">        newThr = (<span class="keyword">int</span>)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果newThr还没有被赋值，那么就根据newCap计算出阈值</span></span><br><span class="line">    <span class="keyword">if</span> (newThr == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">float</span> ft = (<span class="keyword">float</span>)newCap * loadFactor;</span><br><span class="line">        newThr = (newCap &lt; MAXIMUM_CAPACITY &amp;&amp; ft &lt; (<span class="keyword">float</span>)MAXIMUM_CAPACITY ?</span><br><span class="line">                  (<span class="keyword">int</span>)ft : Integer.MAX_VALUE);</span><br><span class="line">    &#125;</span><br><span class="line">    threshold = newThr;</span><br><span class="line">    <span class="meta">@SuppressWarnings(&#123;&quot;rawtypes&quot;,&quot;unchecked&quot;&#125;)</span></span><br><span class="line">        Node&lt;K,V&gt;[] newTab = (Node&lt;K,V&gt;[])<span class="keyword">new</span> Node[newCap];</span><br><span class="line">    table = newTab;</span><br><span class="line">    <span class="comment">// 如果oldTab != null，代表这是扩容操作</span></span><br><span class="line">    <span class="comment">// 需要将扩容前的数组数据迁移到新数组</span></span><br><span class="line">    <span class="keyword">if</span> (oldTab != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 遍历oldTab的每一个bucket，然后移动到newTab</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; oldCap; ++j) &#123;</span><br><span class="line">            Node&lt;K,V&gt; e;</span><br><span class="line">            <span class="keyword">if</span> ((e = oldTab[j]) != <span class="keyword">null</span>) &#123;</span><br><span class="line">                oldTab[j] = <span class="keyword">null</span>;</span><br><span class="line">                <span class="comment">// 索引j的bucket只有一个Entry（未发生过碰撞）</span></span><br><span class="line">                <span class="comment">// 直接移动到newTab</span></span><br><span class="line">                <span class="keyword">if</span> (e.next == <span class="keyword">null</span>)</span><br><span class="line">                    newTab[e.hash &amp; (newCap - <span class="number">1</span>)] = e;</span><br><span class="line">                <span class="comment">// 如果是一个树节点（代表已经转换成红黑树了）</span></span><br><span class="line">                <span class="comment">// 那么就将这个节点拆分为lower和upper两棵树</span></span><br><span class="line">                <span class="comment">// 首先会对这个节点进行遍历</span></span><br><span class="line">                <span class="comment">// 只要当前节点的hash &amp; oldCap == 0就链接到lower树</span></span><br><span class="line">                <span class="comment">// 注意这里是与oldCap进行与运算，而不是oldCap - 1(n - 1)</span></span><br><span class="line">                <span class="comment">// oldCap就是扩容后新增有效位的掩码</span></span><br><span class="line">                <span class="comment">// 比如oldCap=16，二进制10000，n-1 = 1111，扩容后的n-1 = 11111</span></span><br><span class="line">                <span class="comment">// 只要hash &amp; oldCap == 0，就代表hash的新增有效位为0</span></span><br><span class="line">                <span class="comment">// 否则就链接到upper树（新增有效位为1）</span></span><br><span class="line">                <span class="comment">// lower会被放入newTab[原索引j]，upper树会被放到newTab[原索引j + oldCap]</span></span><br><span class="line">                <span class="comment">// 如果lower或者upper树的节点少于阈值，会被退化成链表</span></span><br><span class="line">                <span class="keyword">else</span> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line">                    ((TreeNode&lt;K,V&gt;)e).split(<span class="keyword">this</span>, newTab, j, oldCap);</span><br><span class="line">                <span class="keyword">else</span> &#123; <span class="comment">// preserve order</span></span><br><span class="line">                    <span class="comment">// 下面操作的逻辑与分裂树节点基本一致</span></span><br><span class="line">                    <span class="comment">// 只不过split()操作的是TreeNode</span></span><br><span class="line">                    <span class="comment">// 而且会将两条TreeNode链表组织成红黑树</span></span><br><span class="line">                    Node&lt;K,V&gt; loHead = <span class="keyword">null</span>, loTail = <span class="keyword">null</span>;</span><br><span class="line">                    Node&lt;K,V&gt; hiHead = <span class="keyword">null</span>, hiTail = <span class="keyword">null</span>;</span><br><span class="line">                    Node&lt;K,V&gt; next;</span><br><span class="line">                    <span class="keyword">do</span> &#123;</span><br><span class="line">                        next = e.next;</span><br><span class="line">                        <span class="keyword">if</span> ((e.hash &amp; oldCap) == <span class="number">0</span>) &#123;</span><br><span class="line">                            <span class="keyword">if</span> (loTail == <span class="keyword">null</span>)</span><br><span class="line">                                loHead = e;</span><br><span class="line">                            <span class="keyword">else</span></span><br><span class="line">                                loTail.next = e;</span><br><span class="line">                            loTail = e;</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">else</span> &#123;</span><br><span class="line">                            <span class="keyword">if</span> (hiTail == <span class="keyword">null</span>)</span><br><span class="line">                                hiHead = e;</span><br><span class="line">                            <span class="keyword">else</span></span><br><span class="line">                                hiTail.next = e;</span><br><span class="line">                            hiTail = e;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125; <span class="keyword">while</span> ((e = next) != <span class="keyword">null</span>);</span><br><span class="line">                    <span class="keyword">if</span> (loTail != <span class="keyword">null</span>) &#123;</span><br><span class="line">                        loTail.next = <span class="keyword">null</span>;</span><br><span class="line">                        newTab[j] = loHead;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">if</span> (hiTail != <span class="keyword">null</span>) &#123;</span><br><span class="line">                        hiTail.next = <span class="keyword">null</span>;</span><br><span class="line">                        newTab[j + oldCap] = hiHead;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> newTab;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>使用HashMap时还需要注意一点，它不会动态地进行缩容，也就是说，你不应该保留一个已经删除过大量Entry的HashMap（如果不打算继续添加元素的话），此时它的buckets数组经过多次扩容已经变得非常大了，这会占用非常多的无用内存，这样做的好处是不用多次对数组进行扩容或缩容操作。不过一般也不会出现这种情况，如果遇见了，请毫不犹豫地丢掉它，或者把数据转移到一个新的HashMap。</p>
<h4 id="添加元素"><a href="#添加元素" class="headerlink" title="添加元素"></a>添加元素</h4><hr>
<p>我们已经了解了HashMap的内部实现与工作原理，它在内部维护了一个数组，每一个key都会经过散列函数得出在数组的索引，如果两个key的索引相同，那么就使用分离链接法解决碰撞冲突，当Entry的数量大于临界值时，对数组进行扩容。</p>
<p>接下来以一个添加元素（<code>put()</code>）的过程为例来梳理一下知识，下图是<code>put()</code>函数的流程图：</p>
<p><img src="http://wx3.sinaimg.cn/large/63503acbly1fpu3a8bhl4j20xp13kn0l.jpg" alt="put()流程"></p>
<p>然后是源码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> putVal(hash(key), key, value, <span class="keyword">false</span>, <span class="keyword">true</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">putVal</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, <span class="keyword">boolean</span> onlyIfAbsent,</span></span></span><br><span class="line"><span class="function"><span class="params">               <span class="keyword">boolean</span> evict)</span> </span>&#123;</span><br><span class="line">    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p; <span class="keyword">int</span> n, i;</span><br><span class="line">    <span class="comment">// table == null or table.length == 0</span></span><br><span class="line">    <span class="comment">// 第一次调用put()，初始化table</span></span><br><span class="line">    <span class="keyword">if</span> ((tab = table) == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line">        n = (tab = resize()).length;</span><br><span class="line">    <span class="comment">// 没有发生碰撞，直接放入到数组</span></span><br><span class="line">    <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) &amp; hash]) == <span class="keyword">null</span>)</span><br><span class="line">        tab[i] = newNode(hash, key, value, <span class="keyword">null</span>);</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        Node&lt;K,V&gt; e; K k;</span><br><span class="line">        <span class="comment">// 发生碰撞（头节点就是目标节点）</span></span><br><span class="line">        <span class="keyword">if</span> (p.hash == hash &amp;&amp;</span><br><span class="line">            ((k = p.key) == key || (key != <span class="keyword">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">            e = p;</span><br><span class="line">        <span class="comment">// 节点为红黑树</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line">            e = ((TreeNode&lt;K,V&gt;)p).putTreeVal(<span class="keyword">this</span>, tab, hash, key, value);</span><br><span class="line">        <span class="comment">// 节点为链表</span></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> binCount = <span class="number">0</span>; ; ++binCount) &#123;</span><br><span class="line">                <span class="comment">// 未找到目标节点，在链表尾部链接新节点</span></span><br><span class="line">                <span class="keyword">if</span> ((e = p.next) == <span class="keyword">null</span>) &#123;</span><br><span class="line">                    p.next = newNode(hash, key, value, <span class="keyword">null</span>);</span><br><span class="line">                    <span class="keyword">if</span> (binCount &gt;= TREEIFY_THRESHOLD - <span class="number">1</span>) <span class="comment">// -1 for 1st</span></span><br><span class="line">                        <span class="comment">// 链表过长，转换为红黑树</span></span><br><span class="line">                        treeifyBin(tab, hash);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 找到目标节点，退出循环</span></span><br><span class="line">                <span class="keyword">if</span> (e.hash == hash &amp;&amp;</span><br><span class="line">                    ((k = e.key) == key || (key != <span class="keyword">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                p = e;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 节点已存在，替换value</span></span><br><span class="line">        <span class="keyword">if</span> (e != <span class="keyword">null</span>) &#123; <span class="comment">// existing mapping for key</span></span><br><span class="line">            V oldValue = e.value;</span><br><span class="line">            <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="keyword">null</span>)</span><br><span class="line">                e.value = value;</span><br><span class="line">            <span class="comment">// afterNodeXXXXX是提供给LinkedHashMap重写的函数</span></span><br><span class="line">            <span class="comment">// 在HashMap中没有意义</span></span><br><span class="line">            afterNodeAccess(e);</span><br><span class="line">            <span class="keyword">return</span> oldValue;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ++modCount;</span><br><span class="line">    <span class="comment">// 超过临界值，进行扩容</span></span><br><span class="line">    <span class="keyword">if</span> (++size &gt; threshold)</span><br><span class="line">        resize();</span><br><span class="line">    afterNodeInsertion(evict);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="WeakHashMap"><a href="#WeakHashMap" class="headerlink" title="WeakHashMap"></a>WeakHashMap</h3><hr>
<p>WeakHashMap是一个基于Map接口实现的散列表，实现细节与HashMap类似（都有负载因子、散列函数等等，但没有HashMap那么多优化手段），它的特殊之处在于每个key都是一个弱引用。</p>
<p>首先我们要明白什么是弱引用，Java将引用分为四类（从JDK1.2开始），强度依次逐渐减弱：</p>
<ul>
<li><p>强引用： 就是平常使用的普通引用对象，例如<code>Object obj = new Object()</code>，这就是一个强引用，强引用只要还存在，就不会被垃圾收集器回收。</p>
</li>
<li><p>软引用： 软引用表示一个还有用但并非必需的对象，不像强引用，它还需要通过SoftReference类来间接引用目标对象（除了强引用都是如此）。被软引用关联的对象，在将要发生内存溢出异常之前，会被放入回收范围之中以进行第二次回收（如果第二次回收之后依旧没有足够的内存，那么就会抛出OOM异常）。</p>
</li>
<li><p>弱引用： 同样是表示一个非必需的对象，但要比软引用的强度还要弱，需要通过WeakReference类来间接引用目标对象。被弱引用关联的对象只能存活到下一次垃圾回收发生之前，当触发垃圾回收时，无论当前内存是否足够，都会回收掉只被弱引用关联的对象（如果这个对象还被强引用所引用，那么就不会被回收）。</p>
</li>
<li><p>虚引用： 这是一种最弱的引用关系，需要通过PhantomReference类来间接引用目标对象。一个对象是否有虚引用的存在，完全不会对其生存时间构成影响，也无法通过虚引用来获得对象实例。虚引用的唯一作用就是能在这个对象被回收时收到一个系统通知（结合ReferenceQueue使用）。基于这点可以通过虚引用来实现对象的析构函数，这比使用<code>finalize()</code>函数是要靠谱多了。</p>
</li>
</ul>
<p>WeakHashMap适合用来当做一个缓存来使用。假设你的缓存系统是基于强引用实现的，那么你就必须以手动（或者用一条线程来不断轮询）的方式来删除一个无效的缓存项，而基于弱引用实现的缓存项只要没被其他强引用对象关联，就会被直接放入回收队列。</p>
<p>需要注意的是，只有key是被弱引用关联的，而value一般都是一个强引用对象。因此，需要确保value没有关联到它的key，否则会对key的回收产生阻碍。在极端的情况下，一个value对象A引用了另一个key对象D，而与D相对应的value对象C又反过来引用了与A相对应的key对象B，这就会产生一个引用循环，导致D与B都无法被正常回收。想要解决这个问题，就只能把value也变成一个弱引用，例如<code>m.put(key, new WeakReference(value))</code>，弱引用之间的互相引用不会产生影响。</p>
<p>查找操作的实现跟HashMap相比简单了许多，只要读懂了HashMap，基本都能看懂，源码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Value representing null keys inside tables.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Object NULL_KEY = <span class="keyword">new</span> Object();</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Use NULL_KEY for key if it is null.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Object <span class="title">maskNull</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> (key == <span class="keyword">null</span>) ? NULL_KEY : key;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns index for hash code h.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">indexFor</span><span class="params">(<span class="keyword">int</span> h, <span class="keyword">int</span> length)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> h &amp; (length-<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// WeakHashMap允许null key与null value</span></span><br><span class="line">    <span class="comment">// null key会被替换为一个虚拟值</span></span><br><span class="line">    Object k = maskNull(key); </span><br><span class="line">    <span class="keyword">int</span> h = hash(k);</span><br><span class="line">    Entry&lt;K,V&gt;[] tab = getTable();</span><br><span class="line">    <span class="keyword">int</span> index = indexFor(h, tab.length);</span><br><span class="line">    Entry&lt;K,V&gt; e = tab[index];</span><br><span class="line">    <span class="comment">// 遍历链表</span></span><br><span class="line">    <span class="keyword">while</span> (e != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (e.hash == h &amp;&amp; eq(k, e.get()))</span><br><span class="line">            <span class="keyword">return</span> e.value;</span><br><span class="line">        e = e.next;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>尽管key是一个弱引用，但仍需手动地回收那些已经无效的Entry。这个操作会在<code>getTable()</code>函数中执行，不管是查找、添加还是删除，都需要调用<code>getTable()</code>来获得buckets数组，所以这是种防止内存泄漏的被动保护措施。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The table, resized as necessary. Length MUST Always be a power of two.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">Entry&lt;K,V&gt;[] table;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Reference queue for cleared WeakEntries</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ReferenceQueue&lt;Object&gt; queue = <span class="keyword">new</span> ReferenceQueue&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Expunges stale entries from the table.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">expungeStaleEntries</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 遍历ReferenceQueue，然后清理table中无效的Entry</span></span><br><span class="line">    <span class="keyword">for</span> (Object x; (x = queue.poll()) != <span class="keyword">null</span>; ) &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (queue) &#123;</span><br><span class="line">            <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">                Entry&lt;K,V&gt; e = (Entry&lt;K,V&gt;) x;</span><br><span class="line">            <span class="keyword">int</span> i = indexFor(e.hash, table.length);</span><br><span class="line"></span><br><span class="line">            Entry&lt;K,V&gt; prev = table[i];</span><br><span class="line">            Entry&lt;K,V&gt; p = prev;</span><br><span class="line">            <span class="keyword">while</span> (p != <span class="keyword">null</span>) &#123;</span><br><span class="line">                Entry&lt;K,V&gt; next = p.next;</span><br><span class="line">                <span class="keyword">if</span> (p == e) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (prev == e)</span><br><span class="line">                        table[i] = next;</span><br><span class="line">                    <span class="keyword">else</span></span><br><span class="line">                        prev.next = next;</span><br><span class="line">                    <span class="comment">// Must not null out e.next;</span></span><br><span class="line">                    <span class="comment">// stale entries may be in use by a HashIterator</span></span><br><span class="line">                    e.value = <span class="keyword">null</span>; <span class="comment">// Help GC</span></span><br><span class="line">                    size--;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                prev = p;</span><br><span class="line">                p = next;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns the table after first expunging stale entries.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> Entry&lt;K,V&gt;[] getTable() &#123;</span><br><span class="line">    expungeStaleEntries();</span><br><span class="line">    <span class="keyword">return</span> table;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然后是插入操作与删除操作，实现都比较简单：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">    Object k = maskNull(key);</span><br><span class="line">    <span class="keyword">int</span> h = hash(k);</span><br><span class="line">    Entry&lt;K,V&gt;[] tab = getTable();</span><br><span class="line">    <span class="keyword">int</span> i = indexFor(h, tab.length);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (Entry&lt;K,V&gt; e = tab[i]; e != <span class="keyword">null</span>; e = e.next) &#123;</span><br><span class="line">        <span class="keyword">if</span> (h == e.hash &amp;&amp; eq(k, e.get())) &#123;</span><br><span class="line">            V oldValue = e.value;</span><br><span class="line">            <span class="keyword">if</span> (value != oldValue)</span><br><span class="line">                e.value = value;</span><br><span class="line">            <span class="keyword">return</span> oldValue;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    modCount++;</span><br><span class="line">    Entry&lt;K,V&gt; e = tab[i];</span><br><span class="line">    <span class="comment">// e被连接在new Entry的后面</span></span><br><span class="line">    tab[i] = <span class="keyword">new</span> Entry&lt;&gt;(k, value, queue, h, e);</span><br><span class="line">    <span class="keyword">if</span> (++size &gt;= threshold)</span><br><span class="line">        resize(tab.length * <span class="number">2</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">remove</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">    Object k = maskNull(key);</span><br><span class="line">    <span class="keyword">int</span> h = hash(k);</span><br><span class="line">    Entry&lt;K,V&gt;[] tab = getTable();</span><br><span class="line">    <span class="keyword">int</span> i = indexFor(h, tab.length);</span><br><span class="line">    Entry&lt;K,V&gt; prev = tab[i];</span><br><span class="line">    Entry&lt;K,V&gt; e = prev;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (e != <span class="keyword">null</span>) &#123;</span><br><span class="line">        Entry&lt;K,V&gt; next = e.next;</span><br><span class="line">        <span class="keyword">if</span> (h == e.hash &amp;&amp; eq(k, e.get())) &#123;</span><br><span class="line">            modCount++;</span><br><span class="line">            size--;</span><br><span class="line">            <span class="keyword">if</span> (prev == e)</span><br><span class="line">                tab[i] = next;</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                prev.next = next;</span><br><span class="line">            <span class="keyword">return</span> e.value;</span><br><span class="line">        &#125;</span><br><span class="line">        prev = e;</span><br><span class="line">        e = next;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们并没有在<code>put()</code>函数中发现key被转换成弱引用，这是怎么回事？key只有在第一次被放入buckets数组时才需要转换成弱引用，也就是<code>new Entry&lt;&gt;(k, value, queue, h, e)</code>，WeakHashMap的Entry实现其实就是WeakReference的子类。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The entries in this hash table extend WeakReference, using its main ref</span></span><br><span class="line"><span class="comment"> * field as the key.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">WeakReference</span>&lt;<span class="title">Object</span>&gt; <span class="keyword">implements</span> <span class="title">Map</span>.<span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    V value;</span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">int</span> hash;</span><br><span class="line">    Entry&lt;K,V&gt; next;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Creates new entry.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Entry(Object key, V value,</span><br><span class="line">          ReferenceQueue&lt;Object&gt; queue,</span><br><span class="line">          <span class="keyword">int</span> hash, Entry&lt;K,V&gt; next) &#123;</span><br><span class="line">        <span class="keyword">super</span>(key, queue);</span><br><span class="line">        <span class="keyword">this</span>.value = value;</span><br><span class="line">        <span class="keyword">this</span>.hash  = hash;</span><br><span class="line">        <span class="keyword">this</span>.next  = next;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> K <span class="title">getKey</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> (K) WeakHashMap.unmaskNull(get());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> V <span class="title">getValue</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> value;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> V <span class="title">setValue</span><span class="params">(V newValue)</span> </span>&#123;</span><br><span class="line">        V oldValue = value;</span><br><span class="line">        value = newValue;</span><br><span class="line">        <span class="keyword">return</span> oldValue;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object o)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> Map.Entry))</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        Map.Entry&lt;?,?&gt; e = (Map.Entry&lt;?,?&gt;)o;</span><br><span class="line">        K k1 = getKey();</span><br><span class="line">        Object k2 = e.getKey();</span><br><span class="line">        <span class="keyword">if</span> (k1 == k2 || (k1 != <span class="keyword">null</span> &amp;&amp; k1.equals(k2))) &#123;</span><br><span class="line">            V v1 = getValue();</span><br><span class="line">            Object v2 = e.getValue();</span><br><span class="line">            <span class="keyword">if</span> (v1 == v2 || (v1 != <span class="keyword">null</span> &amp;&amp; v1.equals(v2)))</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        K k = getKey();</span><br><span class="line">        V v = getValue();</span><br><span class="line">        <span class="keyword">return</span> Objects.hashCode(k) ^ Objects.hashCode(v);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> getKey() + <span class="string">&quot;=&quot;</span> + getValue();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>有关使用WeakReference的一个典型案例是ThreadLocal，感兴趣的读者可以参考我之前写的博客<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/2017/11/06/2017-11-06-spring_and_thread-safe/">聊一聊Spring中的线程安全性</a>。</p>
<h3 id="LinkedHashMap"><a href="#LinkedHashMap" class="headerlink" title="LinkedHashMap"></a>LinkedHashMap</h3><hr>
<p>LinkedHashMap继承HashMap并实现了Map接口，同时具有可预测的迭代顺序（按照插入顺序排序）。它与HashMap的不同之处在于，维护了一条贯穿其全部Entry的双向链表（因为额外维护了链表的关系，性能上要略差于HashMap，不过集合视图的遍历时间与元素数量成正比，而HashMap是与buckets数组的长度成正比的），可以认为它是散列表与链表的结合。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The head (eldest) of the doubly linked list.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">transient</span> LinkedHashMap.Entry&lt;K,V&gt; head;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The tail (youngest) of the doubly linked list.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">transient</span> LinkedHashMap.Entry&lt;K,V&gt; tail;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 迭代顺序模式的标记位，如果为true，采用访问排序，否则，采用插入顺序</span></span><br><span class="line"><span class="comment"> * 默认插入顺序（构造函数中默认设置为false）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">boolean</span> accessOrder;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Constructs an empty insertion-ordered &lt;tt&gt;LinkedHashMap&lt;/tt&gt; instance</span></span><br><span class="line"><span class="comment"> * with the default initial capacity (16) and load factor (0.75).</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">LinkedHashMap</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">super</span>();</span><br><span class="line">    accessOrder = <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>LinkedHashMap的Entry实现也继承自HashMap，只不过多了指向前后的两个指针。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * HashMap.Node subclass for normal LinkedHashMap entries.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">HashMap</span>.<span class="title">Node</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    Entry&lt;K,V&gt; before, after;</span><br><span class="line">    Entry(<span class="keyword">int</span> hash, K key, V value, Node&lt;K,V&gt; next) &#123;</span><br><span class="line">        <span class="keyword">super</span>(hash, key, value, next);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>你也可以通过构造函数来构造一个迭代顺序为访问顺序（accessOrder设为true）的LinkedHashMap，这个访问顺序指的是按照最近被访问的Entry的顺序进行排序（从最近最少访问到最近最多访问）。基于这点可以简单实现一个采用LRU（Least Recently Used）策略的缓存。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">LinkedHashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity,</span></span></span><br><span class="line"><span class="function"><span class="params">                     <span class="keyword">float</span> loadFactor,</span></span></span><br><span class="line"><span class="function"><span class="params">                     <span class="keyword">boolean</span> accessOrder)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">super</span>(initialCapacity, loadFactor);</span><br><span class="line">    <span class="keyword">this</span>.accessOrder = accessOrder;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>LinkedHashMap复用了HashMap的大部分代码，所以它的查找实现是非常简单的，唯一稍微复杂点的操作是保证访问顺序。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">    Node&lt;K,V&gt; e;</span><br><span class="line">    <span class="keyword">if</span> ((e = getNode(hash(key), key)) == <span class="keyword">null</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">if</span> (accessOrder)</span><br><span class="line">        afterNodeAccess(e);</span><br><span class="line">    <span class="keyword">return</span> e.value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>还记得这些afterNodeXXXX命名格式的函数吗？我们之前已经在HashMap中见识过了，这些函数在HashMap中只是一个空实现，是专门用来让LinkedHashMap重写实现的hook函数。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">   <span class="comment">// 在HashMap.removeNode()的末尾处调用</span></span><br><span class="line">   <span class="comment">// 将e从LinkedHashMap的双向链表中删除</span></span><br><span class="line">   <span class="function"><span class="keyword">void</span> <span class="title">afterNodeRemoval</span><span class="params">(Node&lt;K,V&gt; e)</span> </span>&#123; <span class="comment">// unlink</span></span><br><span class="line">       LinkedHashMap.Entry&lt;K,V&gt; p =</span><br><span class="line">           (LinkedHashMap.Entry&lt;K,V&gt;)e, b = p.before, a = p.after;</span><br><span class="line">       p.before = p.after = <span class="keyword">null</span>;</span><br><span class="line">       <span class="keyword">if</span> (b == <span class="keyword">null</span>)</span><br><span class="line">           head = a;</span><br><span class="line">       <span class="keyword">else</span></span><br><span class="line">           b.after = a;</span><br><span class="line">       <span class="keyword">if</span> (a == <span class="keyword">null</span>)</span><br><span class="line">           tail = b;</span><br><span class="line">       <span class="keyword">else</span></span><br><span class="line">           a.before = b;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// 在HashMap.putVal()的末尾处调用</span></span><br><span class="line">   <span class="comment">// evict是一个模式标记，如果为false代表buckets数组处于创建模式</span></span><br><span class="line">   <span class="comment">// HashMap.put()函数对此标记设置为true</span></span><br><span class="line">   <span class="function"><span class="keyword">void</span> <span class="title">afterNodeInsertion</span><span class="params">(<span class="keyword">boolean</span> evict)</span> </span>&#123; <span class="comment">// possibly remove eldest</span></span><br><span class="line">       LinkedHashMap.Entry&lt;K,V&gt; first;</span><br><span class="line">       <span class="comment">// LinkedHashMap.removeEldestEntry()永远返回false</span></span><br><span class="line">       <span class="comment">// 避免了最年长元素被删除的可能（就像一个普通的Map一样）</span></span><br><span class="line">       <span class="keyword">if</span> (evict &amp;&amp; (first = head) != <span class="keyword">null</span> &amp;&amp; removeEldestEntry(first)) &#123;</span><br><span class="line">           K key = first.key;</span><br><span class="line">           removeNode(hash(key), key, <span class="keyword">null</span>, <span class="keyword">false</span>, <span class="keyword">true</span>);</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// HashMap.get()没有调用此函数，所以LinkedHashMap重写了get()</span></span><br><span class="line"><span class="comment">// get()与put()都会调用afterNodeAccess()来保证访问顺序</span></span><br><span class="line">   <span class="comment">// 将e移动到tail，代表最近访问到的节点</span></span><br><span class="line">   <span class="function"><span class="keyword">void</span> <span class="title">afterNodeAccess</span><span class="params">(Node&lt;K,V&gt; e)</span> </span>&#123; <span class="comment">// move node to last</span></span><br><span class="line">       LinkedHashMap.Entry&lt;K,V&gt; last;</span><br><span class="line">       <span class="keyword">if</span> (accessOrder &amp;&amp; (last = tail) != e) &#123;</span><br><span class="line">           LinkedHashMap.Entry&lt;K,V&gt; p =</span><br><span class="line">               (LinkedHashMap.Entry&lt;K,V&gt;)e, b = p.before, a = p.after;</span><br><span class="line">           p.after = <span class="keyword">null</span>;</span><br><span class="line">           <span class="keyword">if</span> (b == <span class="keyword">null</span>)</span><br><span class="line">               head = a;</span><br><span class="line">           <span class="keyword">else</span></span><br><span class="line">               b.after = a;</span><br><span class="line">           <span class="keyword">if</span> (a != <span class="keyword">null</span>)</span><br><span class="line">               a.before = b;</span><br><span class="line">           <span class="keyword">else</span></span><br><span class="line">               last = b;</span><br><span class="line">           <span class="keyword">if</span> (last == <span class="keyword">null</span>)</span><br><span class="line">               head = p;</span><br><span class="line">           <span class="keyword">else</span> &#123;</span><br><span class="line">               p.before = last;</span><br><span class="line">               last.after = p;</span><br><span class="line">           &#125;</span><br><span class="line">           tail = p;</span><br><span class="line">           ++modCount;</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>
<p>注意<code>removeEldestEntry()</code>默认永远返回false，这时它的行为与普通的Map无异。如果你把<code>removeEldestEntry()</code>重写为永远返回true，那么就有可能使LinkedHashMap处于一个永远为空的状态（每次<code>put()</code>或者<code>putAll()</code>都会删除头节点）。</p>
<p>一个比较合理的实现示例：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">removeEldestEntry</span><span class="params">(Map.Entry eldest)</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> size() &gt; MAX_SIZE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>LinkedHashMap重写了<code>newNode()</code>等函数，以初始化或连接节点到它内部的双向链表：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">// 链接节点p到链表尾部（或初始化链表）</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">linkNodeLast</span><span class="params">(LinkedHashMap.Entry&lt;K,V&gt; p)</span> </span>&#123;</span><br><span class="line">    LinkedHashMap.Entry&lt;K,V&gt; last = tail;</span><br><span class="line">    tail = p;</span><br><span class="line">    <span class="keyword">if</span> (last == <span class="keyword">null</span>)</span><br><span class="line">        head = p;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        p.before = last;</span><br><span class="line">        last.after = p;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用dst替换掉src</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">transferLinks</span><span class="params">(LinkedHashMap.Entry&lt;K,V&gt; src,</span></span></span><br><span class="line"><span class="function"><span class="params">                           LinkedHashMap.Entry&lt;K,V&gt; dst)</span> </span>&#123;</span><br><span class="line">    LinkedHashMap.Entry&lt;K,V&gt; b = dst.before = src.before;</span><br><span class="line">    LinkedHashMap.Entry&lt;K,V&gt; a = dst.after = src.after;</span><br><span class="line">    <span class="comment">// src是头节点</span></span><br><span class="line">    <span class="keyword">if</span> (b == <span class="keyword">null</span>)</span><br><span class="line">        head = dst;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        b.after = dst;</span><br><span class="line">    <span class="comment">// src是尾节点</span></span><br><span class="line">    <span class="keyword">if</span> (a == <span class="keyword">null</span>)</span><br><span class="line">        tail = dst;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        a.before = dst;</span><br><span class="line">&#125;   </span><br><span class="line"></span><br><span class="line"><span class="function">Node&lt;K,V&gt; <span class="title">newNode</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, Node&lt;K,V&gt; e)</span> </span>&#123;</span><br><span class="line">    LinkedHashMap.Entry&lt;K,V&gt; p =</span><br><span class="line">        <span class="keyword">new</span> LinkedHashMap.Entry&lt;K,V&gt;(hash, key, value, e);</span><br><span class="line">    linkNodeLast(p);</span><br><span class="line">    <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">Node&lt;K,V&gt; <span class="title">replacementNode</span><span class="params">(Node&lt;K,V&gt; p, Node&lt;K,V&gt; next)</span> </span>&#123;</span><br><span class="line">    LinkedHashMap.Entry&lt;K,V&gt; q = (LinkedHashMap.Entry&lt;K,V&gt;)p;</span><br><span class="line">    LinkedHashMap.Entry&lt;K,V&gt; t =</span><br><span class="line">        <span class="keyword">new</span> LinkedHashMap.Entry&lt;K,V&gt;(q.hash, q.key, q.value, next);</span><br><span class="line">    transferLinks(q, t);</span><br><span class="line">    <span class="keyword">return</span> t;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">TreeNode&lt;K,V&gt; <span class="title">newTreeNode</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, Node&lt;K,V&gt; next)</span> </span>&#123;</span><br><span class="line">    TreeNode&lt;K,V&gt; p = <span class="keyword">new</span> TreeNode&lt;K,V&gt;(hash, key, value, next);</span><br><span class="line">    linkNodeLast(p);</span><br><span class="line">    <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">TreeNode&lt;K,V&gt; <span class="title">replacementTreeNode</span><span class="params">(Node&lt;K,V&gt; p, Node&lt;K,V&gt; next)</span> </span>&#123;</span><br><span class="line">    LinkedHashMap.Entry&lt;K,V&gt; q = (LinkedHashMap.Entry&lt;K,V&gt;)p;</span><br><span class="line">    TreeNode&lt;K,V&gt; t = <span class="keyword">new</span> TreeNode&lt;K,V&gt;(q.hash, q.key, q.value, next);</span><br><span class="line">    transferLinks(q, t);</span><br><span class="line">    <span class="keyword">return</span> t;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>遍历LinkedHashMap所需要的时间与Entry数量成正比，这是因为迭代器直接对双向链表进行迭代，而链表中只会含有Entry节点。迭代的顺序是从头节点开始一直到尾节点，插入操作会将新节点链接到尾部，所以保证了插入顺序，而访问顺序会通过<code>afterNodeAccess()</code>来保证，访问次数越多的节点越接近尾部。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">LinkedHashIterator</span> </span>&#123;</span><br><span class="line">    LinkedHashMap.Entry&lt;K,V&gt; next;</span><br><span class="line">    LinkedHashMap.Entry&lt;K,V&gt; current;</span><br><span class="line">    <span class="keyword">int</span> expectedModCount;</span><br><span class="line"></span><br><span class="line">    LinkedHashIterator() &#123;</span><br><span class="line">        next = head;</span><br><span class="line">        expectedModCount = modCount;</span><br><span class="line">        current = <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">hasNext</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> next != <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> LinkedHashMap.<span class="function">Entry&lt;K,V&gt; <span class="title">nextNode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        LinkedHashMap.Entry&lt;K,V&gt; e = next;</span><br><span class="line">        <span class="keyword">if</span> (modCount != expectedModCount)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException();</span><br><span class="line">        <span class="keyword">if</span> (e == <span class="keyword">null</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> NoSuchElementException();</span><br><span class="line">        current = e;</span><br><span class="line">        next = e.after;</span><br><span class="line">        <span class="keyword">return</span> e;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Node&lt;K,V&gt; p = current;</span><br><span class="line">        <span class="keyword">if</span> (p == <span class="keyword">null</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException();</span><br><span class="line">        <span class="keyword">if</span> (modCount != expectedModCount)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException();</span><br><span class="line">        current = <span class="keyword">null</span>;</span><br><span class="line">        K key = p.key;</span><br><span class="line">        removeNode(hash(key), key, <span class="keyword">null</span>, <span class="keyword">false</span>, <span class="keyword">false</span>);</span><br><span class="line">        expectedModCount = modCount;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">LinkedKeyIterator</span> <span class="keyword">extends</span> <span class="title">LinkedHashIterator</span></span></span><br><span class="line"><span class="class">    <span class="keyword">implements</span> <span class="title">Iterator</span>&lt;<span class="title">K</span>&gt; </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> K <span class="title">next</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> nextNode().getKey(); &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">LinkedValueIterator</span> <span class="keyword">extends</span> <span class="title">LinkedHashIterator</span></span></span><br><span class="line"><span class="class">    <span class="keyword">implements</span> <span class="title">Iterator</span>&lt;<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> V <span class="title">next</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> nextNode().value; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">LinkedEntryIterator</span> <span class="keyword">extends</span> <span class="title">LinkedHashIterator</span></span></span><br><span class="line"><span class="class">    <span class="keyword">implements</span> <span class="title">Iterator</span>&lt;<span class="title">Map</span>.<span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> Map.<span class="function">Entry&lt;K,V&gt; <span class="title">next</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> nextNode(); &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="ConcurrentHashMap"><a href="#ConcurrentHashMap" class="headerlink" title="ConcurrentHashMap"></a>ConcurrentHashMap</h3><hr>
<p>我们上述所讲的Map都是非线程安全的，这意味着不应该在多个线程中对这些Map进行修改操作，轻则会产生数据不一致的问题，甚至还会因为并发插入元素而导致链表成环（插入会触发扩容，而扩容操作需要将原数组中的元素rehash到新数组，这时并发操作就有可能产生链表的循环引用从而成环），这样在查找时就会发生死循环，影响到整个应用程序。</p>
<p><code>Collections.synchronizedMap(Map&lt;K,V&gt; m)</code>可以将一个Map转换成线程安全的实现，其实也就是通过一个包装类，然后把所有功能都委托给传入的Map实现，而且包装类是基于<code>synchronized</code>关键字来保证线程安全的（时代的眼泪Hashtable也是基于<code>synchronized</code>关键字），底层使用的是互斥锁（同一时间内只能由持有锁的线程访问，其他竞争线程进入睡眠状态），性能与吞吐量差强人意。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;K,V&gt; <span class="function">Map&lt;K,V&gt; <span class="title">synchronizedMap</span><span class="params">(Map&lt;K,V&gt; m)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> SynchronizedMap&lt;&gt;(m);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">SynchronizedMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;</span></span><br><span class="line"><span class="class">    <span class="keyword">implements</span> <span class="title">Map</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;, <span class="title">Serializable</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1978198479659022715L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;K,V&gt; m;     <span class="comment">// Backing Map</span></span><br><span class="line">    <span class="keyword">final</span> Object      mutex;        <span class="comment">// Object on which to synchronize</span></span><br><span class="line"></span><br><span class="line">    SynchronizedMap(Map&lt;K,V&gt; m) &#123;</span><br><span class="line">        <span class="keyword">this</span>.m = Objects.requireNonNull(m);</span><br><span class="line">        mutex = <span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    SynchronizedMap(Map&lt;K,V&gt; m, Object mutex) &#123;</span><br><span class="line">        <span class="keyword">this</span>.m = m;</span><br><span class="line">        <span class="keyword">this</span>.mutex = mutex;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">size</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (mutex) &#123;<span class="keyword">return</span> m.size();&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isEmpty</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (mutex) &#123;<span class="keyword">return</span> m.isEmpty();&#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ............</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然而ConcurrentHashMap的实现细节远没有这么简单，因此性能也要高上许多。它没有使用一个全局锁来锁住自己，而是采用了减少锁粒度的方法，尽量减少因为竞争锁而导致的阻塞与冲突，而且ConcurrentHashMap的检索操作是不需要锁的。</p>
<p>在Java 7中，ConcurrentHashMap把内部细分成了若干个小的HashMap，称之为段（Segment），默认被分为16个段。对于一个写操作而言，会先根据hash code进行寻址，得出该Entry应被存放在哪一个Segment，然后只要对该Segment加锁即可。</p>
<p><img src="http://wx4.sinaimg.cn/large/63503acbly1fpu3a98u3fj20uf0i03z8.jpg"></p>
<p>理想情况下，一个默认的ConcurrentHashMap可以同时接受16个线程进行写操作（如果都是对不同Segment进行操作的话）。</p>
<p>分段锁对于<code>size()</code>这样的全局操作来说就没有任何作用了，想要得出Entry的数量就需要遍历所有Segment，获得所有的锁，然后再统计总数。事实上，ConcurrentHashMap会先试图使用无锁的方式统计总数，这个尝试会进行3次，如果在相邻的2次计算中获得的Segment的modCount次数一致，代表这两次计算过程中都没有发生过修改操作，那么就可以当做最终结果返回，否则，就要获得所有Segment的锁，重新计算size。</p>
<p>本文主要讨论的是Java 8的ConcurrentHashMap，它与Java 7的实现差别较大。完全放弃了段的设计，而是变回与HashMap相似的设计，使用buckets数组与分离链接法（同样会在超过阈值时树化，对于构造红黑树的逻辑与HashMap差别不大，只不过需要额外使用CAS来保证线程安全），锁的粒度也被细分到每个数组元素（个人认为这样做的原因是因为HashMap在Java 8中也实现了不少优化，即使碰撞严重，也能保证一定的性能，而且Segment不仅臃肿还有弱一致性的问题存在），所以它的并发级别与数组长度相关（Java 7则是与段数相关）。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The array of bins. Lazily initialized upon first insertion.</span></span><br><span class="line"><span class="comment"> * Size is always a power of two. Accessed directly by iterators.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">transient</span> <span class="keyword">volatile</span> Node&lt;K,V&gt;[] table;</span><br></pre></td></tr></table></figure>
<h4 id="寻址"><a href="#寻址" class="headerlink" title="寻址"></a>寻址</h4><hr>
<p>ConcurrentHashMap的散列函数与HashMap并没有什么区别，同样是把key的hash code的高16位与低16位进行异或运算（因为ConcurrentHashMap的buckets数组长度也永远是一个2的N次方），然后将扰乱后的hash code与数组的长度减一（实际可访问到的最大索引）进行与运算，得出的结果即是目标所在的位置。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 2^31 - 1，int类型的最大值</span></span><br><span class="line"><span class="comment">// 该掩码表示节点hash的可用位，用来保证hash永远为一个正整数</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> HASH_BITS = <span class="number">0x7fffffff</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">spread</span><span class="params">(<span class="keyword">int</span> h)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> (h ^ (h &gt;&gt;&gt; <span class="number">16</span>)) &amp; HASH_BITS;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面是查找操作的源码，实现比较简单。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; e, p; <span class="keyword">int</span> n, eh; K ek;</span><br><span class="line">    <span class="keyword">int</span> h = spread(key.hashCode());</span><br><span class="line">    <span class="keyword">if</span> ((tab = table) != <span class="keyword">null</span> &amp;&amp; (n = tab.length) &gt; <span class="number">0</span> &amp;&amp;</span><br><span class="line">        (e = tabAt(tab, (n - <span class="number">1</span>) &amp; h)) != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((eh = e.hash) == h) &#123;</span><br><span class="line">            <span class="comment">// 先尝试判断链表头是否为目标，如果是就直接返回</span></span><br><span class="line">            <span class="keyword">if</span> ((ek = e.key) == key || (ek != <span class="keyword">null</span> &amp;&amp; key.equals(ek)))</span><br><span class="line">                <span class="keyword">return</span> e.val;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (eh &lt; <span class="number">0</span>)</span><br><span class="line">            <span class="comment">// eh &lt; 0代表这是一个特殊节点（TreeBin或ForwardingNode）</span></span><br><span class="line">            <span class="comment">// 所以直接调用find()进行遍历查找</span></span><br><span class="line">            <span class="keyword">return</span> (p = e.find(h, key)) != <span class="keyword">null</span> ? p.val : <span class="keyword">null</span>;</span><br><span class="line">        <span class="comment">// 遍历链表</span></span><br><span class="line">        <span class="keyword">while</span> ((e = e.next) != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (e.hash == h &amp;&amp;</span><br><span class="line">                ((ek = e.key) == key || (ek != <span class="keyword">null</span> &amp;&amp; key.equals(ek))))</span><br><span class="line">                <span class="keyword">return</span> e.val;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>一个普通的节点（链表节点）的hash不可能小于0（已经在<code>spread()</code>函数中修正过了），所以小于0的只可能是一个特殊节点，它不能用while循环中遍历链表的方式来进行遍历。</p>
<p>TreeBin是红黑树的头部节点（红黑树的节点为TreeNode），它本身不含有key与value，而是指向一个TreeNode节点的链表与它们的根节点，同时使用CAS（ConcurrentHashMap并不是完全基于互斥锁实现的，而是与CAS这种乐观策略搭配使用，以提高性能）实现了一个读写锁，迫使Writer（持有这个锁）在树重构操作之前等待Reader完成。</p>
<p>ForwardingNode是一个在数据转移过程（由扩容引起）中使用的临时节点，它会被插入到头部。它与TreeBin（和TreeNode）都是Node类的子类。</p>
<p>为了判断出哪些是特殊节点，TreeBin和ForwardingNode的hash域都只是一个虚拟值：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">implements</span> <span class="title">Map</span>.<span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">int</span> hash;</span><br><span class="line">    <span class="keyword">final</span> K key;</span><br><span class="line">    <span class="keyword">volatile</span> V val;</span><br><span class="line">    <span class="keyword">volatile</span> Node&lt;K,V&gt; next;</span><br><span class="line"></span><br><span class="line">    Node(<span class="keyword">int</span> hash, K key, V val, Node&lt;K,V&gt; next) &#123;</span><br><span class="line">        <span class="keyword">this</span>.hash = hash;</span><br><span class="line">        <span class="keyword">this</span>.key = key;</span><br><span class="line">        <span class="keyword">this</span>.val = val;</span><br><span class="line">        <span class="keyword">this</span>.next = next;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> V <span class="title">setValue</span><span class="params">(V value)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> UnsupportedOperationException();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ......</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Virtualized support for map.get(); overridden in subclasses.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function">Node&lt;K,V&gt; <span class="title">find</span><span class="params">(<span class="keyword">int</span> h, Object k)</span> </span>&#123;</span><br><span class="line">        Node&lt;K,V&gt; e = <span class="keyword">this</span>;</span><br><span class="line">        <span class="keyword">if</span> (k != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                K ek;</span><br><span class="line">                <span class="keyword">if</span> (e.hash == h &amp;&amp;</span><br><span class="line">                    ((ek = e.key) == k || (ek != <span class="keyword">null</span> &amp;&amp; k.equals(ek))))</span><br><span class="line">                    <span class="keyword">return</span> e;</span><br><span class="line">            &#125; <span class="keyword">while</span> ((e = e.next) != <span class="keyword">null</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Encodings for Node hash fields. See above for explanation.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> MOVED     = -<span class="number">1</span>; <span class="comment">// hash for forwarding nodes</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> TREEBIN   = -<span class="number">2</span>; <span class="comment">// hash for roots of trees</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> RESERVED  = -<span class="number">3</span>; <span class="comment">// hash for transient reservations    </span></span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">TreeBin</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">Node</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    ....</span><br><span class="line"></span><br><span class="line">    TreeBin(TreeNode&lt;K,V&gt; b) &#123;</span><br><span class="line">        <span class="keyword">super</span>(TREEBIN, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line">        ....</span><br><span class="line">    &#125;   </span><br><span class="line">    </span><br><span class="line">    ....     </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">ForwardingNode</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">Node</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Node&lt;K,V&gt;[] nextTable;</span><br><span class="line">    ForwardingNode(Node&lt;K,V&gt;[] tab) &#123;</span><br><span class="line">        <span class="keyword">super</span>(MOVED, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line">        <span class="keyword">this</span>.nextTable = tab;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    .....</span><br><span class="line">&#125;        </span><br></pre></td></tr></table></figure>

<h4 id="可见性"><a href="#可见性" class="headerlink" title="可见性"></a>可见性</h4><hr>
<p>我们在<code>get()</code>函数中并没有发现任何与锁相关的代码，那么它是怎么保证线程安全的呢？一个操作<code>ConcurrentHashMap.get(&quot;a&quot;)</code>，它的步骤基本分为以下几步：</p>
<ul>
<li><p>根据散列函数计算出的索引访问table。</p>
</li>
<li><p>从table中取出头节点。</p>
</li>
<li><p>遍历头节点直到找到目标节点。</p>
</li>
<li><p>从目标节点中取出value并返回。</p>
</li>
</ul>
<p>所以只要保证访问table与节点的操作总是能够返回最新的数据就可以了。ConcurrentHashMap并没有采用锁的方式，而是通过<code>volatile</code>关键字来保证它们的可见性。在上文贴出的代码中可以发现，table、Node.val和Node.next都是被<code>volatile</code>关键字所修饰的。</p>
<p><code>volatile</code>关键字保证了多线程环境下变量的可见性与有序性，底层实现基于内存屏障（Memory Barrier）。</p>
<p>为了优化性能，现代CPU工作时的指令执行顺序与应用程序的代码顺序其实是不一致的（有些编译器也会进行这种优化），也就是所谓的乱序执行技术。乱序执行可以提高CPU流水线的工作效率，只要保证数据符合程序逻辑上的正确性即可（遵循<code>happens-before</code>原则）。不过如今是多核时代，如果随便乱序而不提供防护措施那是会出问题的。每一个cpu上都会进行乱序优化，单cpu所保证的逻辑次序可能会被其他cpu所破坏。</p>
<p>内存屏障就是针对此情况的防护措施。可以认为它是一个同步点（但它本身也是一条cpu指令）。例如在<code>IA32</code>指令集架构中引入的<code>SFENCE</code>指令，在该指令之前的所有写操作必须全部完成，读操作仍可以乱序执行。<code>LFENCE</code>指令则保证之前的所有读操作必须全部完成，另外还有粒度更粗的<code>MFENCE</code>指令保证之前的所有读写操作都必须全部完成。</p>
<p>内存屏障就像是一个保护指令顺序的栅栏，保护后面的指令不被前面的指令跨越。将内存屏障插入到写操作与读操作之间，就可以保证之后的读操作可以访问到最新的数据，因为屏障前的写操作已经把数据写回到内存（根据缓存一致性协议，不会直接写回到内存，而是改变该cpu私有缓存中的状态，然后通知给其他cpu这个缓存行已经被修改过了，之后另一个cpu在读操作时就可以发现该缓存行已经是无效的了，这时它会从其他cpu中读取最新的缓存行，然后之前的cpu才会更改状态并写回到内存）。</p>
<p>例如，读一个被<code>volatile</code>修饰的变量V总是能够从JMM（Java Memory Model）主内存中获得最新的数据。因为内存屏障的原因，每次在使用变量V（通过JVM指令<code>use</code>，后面说的也都是JVM中的指令而不是cpu）之前都必须先执行<code>load</code>指令（把从主内存中得到的数据放入到工作内存），根据JVM的规定，<code>load</code>指令必须发生在<code>read</code>指令（从主内存中读取数据）之后，所以每次访问变量V都会先从主内存中读取。相对的，写操作也因为内存屏障保证的指令顺序，每次都会直接写回到主内存。</p>
<p>不过<code>volatile</code>关键字并不能保证操作的原子性，对该变量进行并发的连续操作是非线程安全的，所幸ConcurrentHashMap只是用来确保访问到的变量是最新的，所以也不会发生什么问题。</p>
<p>出于性能考虑，Doug Lea（<code>java.util.concurrent</code>包的作者）直接通过Unsafe类来对table进行操作。</p>
<p>Java号称是安全的编程语言，而保证安全的代价就是牺牲程序员自由操控内存的能力。像在C/C++中可以通过操作指针变量达到操作内存的目的（其实操作的是虚拟地址），但这种灵活性在新手手中也经常会带来一些愚蠢的错误，比如内存访问越界。</p>
<p>Unsafe从字面意思可以看出是不安全的，它包含了许多本地方法（在JVM平台上运行的其他语言编写的程序，主要为C/C++，由<code>JNI</code>实现），这些方法支持了对指针的操作，所以它才被称为是不安全的。虽然不安全，但毕竟是由C/C++实现的，像一些与操作系统交互的操作肯定是快过Java的，毕竟Java与操作系统之间还隔了一层抽象（JVM），不过代价就是失去了JVM所带来的多平台可移植性（本质上也只是一个c/cpp文件，如果换了平台那就要重新编译）。</p>
<p>对table进行操作的函数有以下三个，都使用到了Unsafe（在<code>java.util.concurrent</code>包随处可见）：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> &lt;K,V&gt; <span class="function">Node&lt;K,V&gt; <span class="title">tabAt</span><span class="params">(Node&lt;K,V&gt;[] tab, <span class="keyword">int</span> i)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 从tab数组中获取一个引用，遵循Volatile语义</span></span><br><span class="line">    <span class="comment">// 参数2是一个在tab中的偏移量，用来寻找目标对象</span></span><br><span class="line">    <span class="keyword">return</span> (Node&lt;K,V&gt;)U.getObjectVolatile(tab, ((<span class="keyword">long</span>)i &lt;&lt; ASHIFT) + ABASE);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> &lt;K,V&gt; <span class="function"><span class="keyword">boolean</span> <span class="title">casTabAt</span><span class="params">(Node&lt;K,V&gt;[] tab, <span class="keyword">int</span> i,</span></span></span><br><span class="line"><span class="function"><span class="params">                                    Node&lt;K,V&gt; c, Node&lt;K,V&gt; v)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 通过CAS操作将tab数组中位于参数2偏移量位置的值替换为v</span></span><br><span class="line">    <span class="comment">// c是期望值，如果期望值与实际值不符，返回false</span></span><br><span class="line">    <span class="comment">// 否则，v会成功地被设置到目标位置，返回true</span></span><br><span class="line">    <span class="keyword">return</span> U.compareAndSwapObject(tab, ((<span class="keyword">long</span>)i &lt;&lt; ASHIFT) + ABASE, c, v);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> &lt;K,V&gt; <span class="function"><span class="keyword">void</span> <span class="title">setTabAt</span><span class="params">(Node&lt;K,V&gt;[] tab, <span class="keyword">int</span> i, Node&lt;K,V&gt; v)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 设置tab数组中位于参数2偏移量位置的值，遵循Volatile语义</span></span><br><span class="line">    U.putObjectVolatile(tab, ((<span class="keyword">long</span>)i &lt;&lt; ASHIFT) + ABASE, v);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如果对Unsafe感兴趣，可以参考这篇文章：<a target="_blank" rel="noopener" href="http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/">Java Magic. Part 4: sun.misc.Unsafe</a></p>
<h4 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h4><hr>
<p>ConcurrentHashMap与HashMap一样是Lazy的，buckets数组会在第一次访问<code>put()</code>函数时进行初始化，它的默认构造函数甚至是个空函数。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Creates a new, empty map with the default initial table size (16).</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ConcurrentHashMap</span><span class="params">()</span> </span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>但是有一点需要注意，ConcurrentHashMap是工作在多线程并发环境下的，如果有多个线程同时调用了<code>put()</code>函数该怎么办？这会导致重复初始化，所以必须要有对应的防护措施。</p>
<p>ConcurrentHashMap声明了一个用于控制table的初始化与扩容的实例变量sizeCtl，默认值为0。当它是一个负数的时候，代表table正处于初始化或者扩容的状态。<code>-1</code>表示table正在进行初始化，<code>-N</code>则表示当前有N-1个线程正在进行扩容。</p>
<p>在其他情况下，如果table还未初始化（<code>table == null</code>），sizeCtl表示table进行初始化的数组大小（所以从构造函数传入的initialCapacity在经过计算后会被赋给它）。如果table已经初始化过了，则表示下次触发扩容操作的阈值，算法<code>stzeCtl = n - (n &gt;&gt;&gt; 2)</code>，也就是n的75%，与默认负载因子（0.75）的HashMap一致。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> <span class="keyword">int</span> sizeCtl;</span><br></pre></td></tr></table></figure>
<p>初始化table的操作位于函数<code>initTable()</code>，源码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Initializes table, using the size recorded in sizeCtl.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Node&lt;K,V&gt;[] initTable() &#123;</span><br><span class="line">    Node&lt;K,V&gt;[] tab; <span class="keyword">int</span> sc;</span><br><span class="line">    <span class="keyword">while</span> ((tab = table) == <span class="keyword">null</span> || tab.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// sizeCtl小于0，这意味着已经有其他线程进行初始化了</span></span><br><span class="line">        <span class="comment">// 所以当前线程让出CPU时间片</span></span><br><span class="line">        <span class="keyword">if</span> ((sc = sizeCtl) &lt; <span class="number">0</span>)</span><br><span class="line">            Thread.yield(); <span class="comment">// lost initialization race; just spin</span></span><br><span class="line">        <span class="comment">// 否则，通过CAS操作尝试修改sizeCtl</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, -<span class="number">1</span>)) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> ((tab = table) == <span class="keyword">null</span> || tab.length == <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="comment">// 默认构造函数，sizeCtl = 0，使用默认容量（16）进行初始化</span></span><br><span class="line">                    <span class="comment">// 否则，会根据sizeCtl进行初始化</span></span><br><span class="line">                    <span class="keyword">int</span> n = (sc &gt; <span class="number">0</span>) ? sc : DEFAULT_CAPACITY;</span><br><span class="line">                    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">                    Node&lt;K,V&gt;[] nt = (Node&lt;K,V&gt;[])<span class="keyword">new</span> Node&lt;?,?&gt;[n];</span><br><span class="line">                    table = tab = nt;</span><br><span class="line">                    <span class="comment">// 计算阈值，n的75%</span></span><br><span class="line">                    sc = n - (n &gt;&gt;&gt; <span class="number">2</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="comment">// 阈值赋给sizeCtl</span></span><br><span class="line">                sizeCtl = sc;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> tab;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>sizeCtl是一个<code>volatile</code>变量，只要有一个线程CAS操作成功，sizeCtl就会被暂时地修改为-1，这样其他线程就能够根据sizeCtl得知table是否已经处于初始化状态中，最后sizeCtl会被设置成阈值，用于触发扩容操作。</p>
<h4 id="扩容"><a href="#扩容" class="headerlink" title="扩容"></a>扩容</h4><hr>
<p>ConcurrentHashMap触发扩容的时机与HashMap类似，要么是在将链表转换成红黑树时判断table数组的长度是否小于阈值（64），如果小于就进行扩容而不是树化，要么就是在添加元素的时候，判断当前Entry数量是否超过阈值，如果超过就进行扩容。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">treeifyBin</span><span class="params">(Node&lt;K,V&gt;[] tab, <span class="keyword">int</span> index)</span> </span>&#123;</span><br><span class="line">    Node&lt;K,V&gt; b; <span class="keyword">int</span> n, sc;</span><br><span class="line">    <span class="keyword">if</span> (tab != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 小于MIN_TREEIFY_CAPACITY，进行扩容</span></span><br><span class="line">        <span class="keyword">if</span> ((n = tab.length) &lt; MIN_TREEIFY_CAPACITY)</span><br><span class="line">            tryPresize(n &lt;&lt; <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((b = tabAt(tab, index)) != <span class="keyword">null</span> &amp;&amp; b.hash &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (b) &#123;</span><br><span class="line">                <span class="comment">// 将链表转换成红黑树...</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">putVal</span><span class="params">(K key, V value, <span class="keyword">boolean</span> onlyIfAbsent)</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    addCount(<span class="number">1L</span>, binCount); <span class="comment">// 计数</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">addCount</span><span class="params">(<span class="keyword">long</span> x, <span class="keyword">int</span> check)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 计数...</span></span><br><span class="line">    <span class="keyword">if</span> (check &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        Node&lt;K,V&gt;[] tab, nt; <span class="keyword">int</span> n, sc;</span><br><span class="line">        <span class="comment">// s(元素个数)大于等于sizeCtl，触发扩容</span></span><br><span class="line">        <span class="keyword">while</span> (s &gt;= (<span class="keyword">long</span>)(sc = sizeCtl) &amp;&amp; (tab = table) != <span class="keyword">null</span> &amp;&amp;</span><br><span class="line">               (n = tab.length) &lt; MAXIMUM_CAPACITY) &#123;</span><br><span class="line">            <span class="comment">// 扩容标志位</span></span><br><span class="line">            <span class="keyword">int</span> rs = resizeStamp(n);</span><br><span class="line">            <span class="comment">// sizeCtl为负数，代表正有其他线程进行扩容</span></span><br><span class="line">            <span class="keyword">if</span> (sc &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 扩容已经结束，中断循环</span></span><br><span class="line">                <span class="keyword">if</span> ((sc &gt;&gt;&gt; RESIZE_STAMP_SHIFT) != rs || sc == rs + <span class="number">1</span> ||</span><br><span class="line">                    sc == rs + MAX_RESIZERS || (nt = nextTable) == <span class="keyword">null</span> ||</span><br><span class="line">                    transferIndex &lt;= <span class="number">0</span>)</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="comment">// 进行扩容，并设置sizeCtl，表示扩容线程 + 1</span></span><br><span class="line">                <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, sc + <span class="number">1</span>))</span><br><span class="line">                    transfer(tab, nt);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 触发扩容（第一个进行扩容的线程）</span></span><br><span class="line">            <span class="comment">// 并设置sizeCtl告知其他线程</span></span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc,</span><br><span class="line">                                         (rs &lt;&lt; RESIZE_STAMP_SHIFT) + <span class="number">2</span>))</span><br><span class="line">                transfer(tab, <span class="keyword">null</span>);</span><br><span class="line">            <span class="comment">// 统计个数，用于循环检测是否还需要扩容</span></span><br><span class="line">            s = sumCount();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到有关sizeCtl的操作牵涉到了大量的位运算，我们先来理解这些位运算的意义。首先是<code>resizeStamp()</code>，该函数返回一个用于数据校验的标志位，意思是对长度为n的table进行扩容。它将n的前导零（最高有效位之前的零的数量）和<code>1 &lt;&lt; 15</code>做或运算，这时低16位的最高位为1，其他都为n的前导零。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">resizeStamp</span><span class="params">(<span class="keyword">int</span> n)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// RESIZE_STAMP_BITS = 16</span></span><br><span class="line">    <span class="keyword">return</span> Integer.numberOfLeadingZeros(n) | (<span class="number">1</span> &lt;&lt; (RESIZE_STAMP_BITS - <span class="number">1</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>初始化sizeCtl（扩容操作被第一个线程首次进行）的算法为<code>(rs &lt;&lt; RESIZE_STAMP_SHIFT) + 2</code>，首先<code>RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS = 16</code>，那么<code>rs &lt;&lt; 16</code>等于将这个标志位移动到了高16位，这时最高位为1，所以sizeCtl此时是个负数，然后加二（至于为什么是2，还记得有关sizeCtl的说明吗？1代表初始化状态，所以实际的线程个数是要减去1的）代表当前有一个线程正在进行扩容，</p>
<p>这样sizeCtl就被分割成了两部分，高16位是一个对n的数据校验的标志位，低16位表示参与扩容操作的线程个数 + 1。</p>
<p>可能会有读者有所疑惑，更新进行扩容的线程数量的操作为什么是<code>sc + 1</code>而不是<code>sc - 1</code>，这是因为对sizeCtl的操作都是基于位运算的，所以不会关心它本身的数值是多少，只关心它在二进制上的数值，而<code>sc + 1</code>会在低16位上加1。</p>
<p><img src="http://wx1.sinaimg.cn/large/63503acbly1fpu3a9r1q0j20vz0gwta5.jpg"></p>
<p><code>tryPresize()</code>函数跟<code>addCount()</code>的后半段逻辑类似，不断地根据sizeCtl判断当前的状态，然后选择对应的策略。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">tryPresize</span><span class="params">(<span class="keyword">int</span> size)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 对size进行修正</span></span><br><span class="line">    <span class="keyword">int</span> c = (size &gt;= (MAXIMUM_CAPACITY &gt;&gt;&gt; <span class="number">1</span>)) ? MAXIMUM_CAPACITY :</span><br><span class="line">        tableSizeFor(size + (size &gt;&gt;&gt; <span class="number">1</span>) + <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">int</span> sc;</span><br><span class="line">    <span class="comment">// sizeCtl是默认值或正整数</span></span><br><span class="line">    <span class="comment">// 代表table还未初始化</span></span><br><span class="line">    <span class="comment">// 或还没有其他线程正在进行扩容</span></span><br><span class="line">    <span class="keyword">while</span> ((sc = sizeCtl) &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        Node&lt;K,V&gt;[] tab = table; <span class="keyword">int</span> n;</span><br><span class="line">        <span class="keyword">if</span> (tab == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>) &#123;</span><br><span class="line">            n = (sc &gt; c) ? sc : c;</span><br><span class="line">            <span class="comment">// 设置sizeCtl，告诉其他线程，table现在正处于初始化状态</span></span><br><span class="line">            <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, -<span class="number">1</span>)) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span> (table == tab) &#123;</span><br><span class="line">                        <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">                        Node&lt;K,V&gt;[] nt = (Node&lt;K,V&gt;[])<span class="keyword">new</span> Node&lt;?,?&gt;[n];</span><br><span class="line">                        table = nt;</span><br><span class="line">                        <span class="comment">// 计算下次触发扩容的阈值</span></span><br><span class="line">                        sc = n - (n &gt;&gt;&gt; <span class="number">2</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    <span class="comment">// 将阈值赋给sizeCtl</span></span><br><span class="line">                    sizeCtl = sc;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 没有超过阈值或者大于容量的上限，中断循环</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (c &lt;= sc || n &gt;= MAXIMUM_CAPACITY)</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="comment">// 进行扩容，与addCount()后半段的逻辑一致</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (tab == table) &#123;</span><br><span class="line">            <span class="keyword">int</span> rs = resizeStamp(n);</span><br><span class="line">            <span class="keyword">if</span> (sc &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                Node&lt;K,V&gt;[] nt;</span><br><span class="line">                <span class="keyword">if</span> ((sc &gt;&gt;&gt; RESIZE_STAMP_SHIFT) != rs || sc == rs + <span class="number">1</span> ||</span><br><span class="line">                    sc == rs + MAX_RESIZERS || (nt = nextTable) == <span class="keyword">null</span> ||</span><br><span class="line">                    transferIndex &lt;= <span class="number">0</span>)</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, sc + <span class="number">1</span>))</span><br><span class="line">                    transfer(tab, nt);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc,</span><br><span class="line">                                         (rs &lt;&lt; RESIZE_STAMP_SHIFT) + <span class="number">2</span>))</span><br><span class="line">                transfer(tab, <span class="keyword">null</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>扩容操作的核心在于数据的转移，在单线程环境下数据的转移很简单，无非就是把旧数组中的数据迁移到新的数组。但是这在多线程环境下是行不通的，需要保证线程安全性，在扩容的时候其他线程也可能正在添加元素，这时又触发了扩容怎么办？有人可能会说，这不难啊，用一个互斥锁把数据转移操作的过程锁住不就好了？这确实是一种可行的解决方法，但同样也会带来极差的吞吐量。    </p>
<p>互斥锁会导致所有访问临界区的线程陷入阻塞状态，这会消耗额外的系统资源，内核需要保存这些线程的上下文并放到阻塞队列，持有锁的线程耗时越长，其他竞争线程就会一直被阻塞，因此吞吐量低下，导致响应时间缓慢。而且锁总是会伴随着死锁问题，一旦发生死锁，整个应用程序都会因此受到影响，所以加锁永远是最后的备选方案。</p>
<p>Doug Lea没有选择直接加锁，而是基于CAS实现无锁的并发同步策略，令人佩服的是他不仅没有把其他线程拒之门外，甚至还邀请它们一起来协助工作。</p>
<p>那么如何才能让多个线程协同工作呢？Doug Lea把整个table数组当做多个线程之间共享的任务队列，然后只需维护一个指针，当有一个线程开始进行数据转移，就会先移动指针，表示指针划过的这片bucket区域由该线程负责。</p>
<p>这个指针被声明为一个<code>volatile</code>整型变量，它的初始位置位于table的尾部，即它等于<code>table.length</code>，很明显这个任务队列是逆向遍历的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The next table index (plus one) to split while resizing.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> <span class="keyword">int</span> transferIndex;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 一个线程需要负责的最小bucket数</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> MIN_TRANSFER_STRIDE = <span class="number">16</span>;</span><br><span class="line">	</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The next table to use; non-null only while resizing.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node&lt;K,V&gt;[] nextTable;	</span><br></pre></td></tr></table></figure>
<p>一个已经迁移完毕的bucket会被替换成ForwardingNode节点，用来标记此bucket已经被其他线程迁移完毕了。我们之前提到过ForwardingNode，它是一个特殊节点，可以通过hash域的虚拟值来识别它，它同样重写了<code>find()</code>函数，用来在新数组中查找目标。</p>
<p>数据迁移的操作位于<code>transfer()</code>函数，多个线程之间依靠sizeCtl与transferIndex指针来协同工作，每个线程都有自己负责的区域，一个完成迁移的bucket会被设置为ForwardingNode，其他线程遇见这个特殊节点就跳过该bucket，处理下一个bucket。</p>
<p><code>transfer()</code>函数可以大致分为三部分，第一部分对后续需要使用的变量进行初始化：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Moves and/or copies the nodes in each bin to new table. See</span></span><br><span class="line"><span class="comment"> * above for explanation.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">transfer</span><span class="params">(Node&lt;K,V&gt;[] tab, Node&lt;K,V&gt;[] nextTab)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> n = tab.length, stride;</span><br><span class="line">    <span class="comment">// 根据当前机器的CPU数量来决定每个线程负责的bucket数</span></span><br><span class="line">    <span class="comment">// 避免因为扩容线程过多，反而影响到性能</span></span><br><span class="line">    <span class="keyword">if</span> ((stride = (NCPU &gt; <span class="number">1</span>) ? (n &gt;&gt;&gt; <span class="number">3</span>) / NCPU : n) &lt; MIN_TRANSFER_STRIDE)</span><br><span class="line">        stride = MIN_TRANSFER_STRIDE; <span class="comment">// subdivide range</span></span><br><span class="line">    <span class="comment">// 初始化nextTab，容量为旧数组的一倍</span></span><br><span class="line">    <span class="keyword">if</span> (nextTab == <span class="keyword">null</span>) &#123;            <span class="comment">// initiating</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">            Node&lt;K,V&gt;[] nt = (Node&lt;K,V&gt;[])<span class="keyword">new</span> Node&lt;?,?&gt;[n &lt;&lt; <span class="number">1</span>];</span><br><span class="line">            nextTab = nt;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable ex) &#123;      <span class="comment">// try to cope with OOME</span></span><br><span class="line">            sizeCtl = Integer.MAX_VALUE;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        nextTable = nextTab;</span><br><span class="line">        transferIndex = n; <span class="comment">// 初始化指针</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">int</span> nextn = nextTab.length;</span><br><span class="line">    ForwardingNode&lt;K,V&gt; fwd = <span class="keyword">new</span> ForwardingNode&lt;K,V&gt;(nextTab);</span><br><span class="line">    <span class="keyword">boolean</span> advance = <span class="keyword">true</span>;</span><br><span class="line">    <span class="keyword">boolean</span> finishing = <span class="keyword">false</span>; <span class="comment">// to ensure sweep before committing nextTab</span></span><br></pre></td></tr></table></figure>
<p>第二部分为当前线程分配任务和控制当前线程的任务进度，这部分是<code>transfer()</code>的核心逻辑，描述了如何与其他线程协同工作：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// i指向当前bucket，bound表示当前线程所负责的bucket区域的边界</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>, bound = <span class="number">0</span>;;) &#123;</span><br><span class="line">    Node&lt;K,V&gt; f; <span class="keyword">int</span> fh;</span><br><span class="line">    <span class="comment">// 这个循环使用CAS不断尝试为当前线程分配任务</span></span><br><span class="line">    <span class="comment">// 直到分配成功或任务队列已经被全部分配完毕</span></span><br><span class="line">    <span class="comment">// 如果当前线程已经被分配过bucket区域</span></span><br><span class="line">    <span class="comment">// 那么会通过--i指向下一个待处理bucket然后退出该循环</span></span><br><span class="line">    <span class="keyword">while</span> (advance) &#123;</span><br><span class="line">        <span class="keyword">int</span> nextIndex, nextBound;</span><br><span class="line">        <span class="comment">// --i表示将i指向下一个待处理的bucket</span></span><br><span class="line">        <span class="comment">// 如果--i &gt;= bound，代表当前线程已经分配过bucket区域</span></span><br><span class="line">        <span class="comment">// 并且还留有未处理的bucket</span></span><br><span class="line">        <span class="keyword">if</span> (--i &gt;= bound || finishing)</span><br><span class="line">            advance = <span class="keyword">false</span>;</span><br><span class="line">        <span class="comment">// transferIndex指针 &lt;= 0 表示所有bucket已经被分配完毕</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((nextIndex = transferIndex) &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">            i = -<span class="number">1</span>;</span><br><span class="line">            advance = <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 移动transferIndex指针</span></span><br><span class="line">        <span class="comment">// 为当前线程设置所负责的bucket区域的范围</span></span><br><span class="line">        <span class="comment">// i指向该范围的第一个bucket，注意i是逆向遍历的</span></span><br><span class="line">        <span class="comment">// 这个范围为(bound, i)，i是该区域最后一个bucket，遍历顺序是逆向的</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt</span><br><span class="line">                 (<span class="keyword">this</span>, TRANSFERINDEX, nextIndex,</span><br><span class="line">                  nextBound = (nextIndex &gt; stride ?</span><br><span class="line">                               nextIndex - stride : <span class="number">0</span>))) &#123;</span><br><span class="line">            bound = nextBound;</span><br><span class="line">            i = nextIndex - <span class="number">1</span>;</span><br><span class="line">            advance = <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 当前线程已经处理完了所负责的所有bucket</span></span><br><span class="line">    <span class="keyword">if</span> (i &lt; <span class="number">0</span> || i &gt;= n || i + n &gt;= nextn) &#123;</span><br><span class="line">        <span class="keyword">int</span> sc;</span><br><span class="line">        <span class="comment">// 如果任务队列已经全部完成</span></span><br><span class="line">        <span class="keyword">if</span> (finishing) &#123;</span><br><span class="line">            nextTable = <span class="keyword">null</span>;</span><br><span class="line">            table = nextTab;</span><br><span class="line">            <span class="comment">// 设置新的阈值</span></span><br><span class="line">            sizeCtl = (n &lt;&lt; <span class="number">1</span>) - (n &gt;&gt;&gt; <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 工作中的扩容线程数量减1</span></span><br><span class="line">        <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc = sizeCtl, sc - <span class="number">1</span>)) &#123;</span><br><span class="line">            <span class="comment">// (resizeStamp &lt;&lt; RESIZE_STAMP_SHIFT) + 2代表当前有一个扩容线程</span></span><br><span class="line">            <span class="comment">// 相对的，(sc - 2) !=  resizeStamp &lt;&lt; RESIZE_STAMP_SHIFT</span></span><br><span class="line">            <span class="comment">// 表示当前还有其他线程正在进行扩容，所以直接返回</span></span><br><span class="line">            <span class="keyword">if</span> ((sc - <span class="number">2</span>) != resizeStamp(n) &lt;&lt; RESIZE_STAMP_SHIFT)</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            <span class="comment">// 否则，当前线程就是最后一个进行扩容的线程</span></span><br><span class="line">            <span class="comment">// 设置finishing标识</span></span><br><span class="line">            finishing = advance = <span class="keyword">true</span>;</span><br><span class="line">            i = n; <span class="comment">// recheck before commit</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果待处理bucket是空的</span></span><br><span class="line">    <span class="comment">// 那么插入ForwardingNode，以通知其他线程</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> ((f = tabAt(tab, i)) == <span class="keyword">null</span>)</span><br><span class="line">        advance = casTabAt(tab, i, <span class="keyword">null</span>, fwd);</span><br><span class="line">    <span class="comment">// 如果待处理bucket的头节点是ForwardingNode</span></span><br><span class="line">    <span class="comment">// 说明此bucket已经被处理过了，跳过该bucket</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> ((fh = f.hash) == MOVED)</span><br><span class="line">        advance = <span class="keyword">true</span>; <span class="comment">// already processed</span></span><br></pre></td></tr></table></figure>
<p>最后一部分是具体的迁移过程（对当前指向的bucket），这部分的逻辑与HashMap类似，拿旧数组的容量当做一个掩码，然后与节点的hash进行与操作，可以得出该节点的新增有效位，如果新增有效位为0就放入一个链表A，如果为1就放入另一个链表B，链表A在新数组中的位置不变（跟在旧数组的索引一致），链表B在新数组中的位置为原索引加上旧数组容量。</p>
<p>这个方法减少了rehash的计算量，而且还能达到均匀分布的目的，如果不能理解请去看本文中HashMap扩容操作的解释。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 对于节点的操作还是要加上锁的</span></span><br><span class="line">    <span class="comment">// 不过这个锁的粒度很小，只锁住了bucket的头节点</span></span><br><span class="line">    <span class="keyword">synchronized</span> (f) &#123;</span><br><span class="line">        <span class="keyword">if</span> (tabAt(tab, i) == f) &#123;</span><br><span class="line">            Node&lt;K,V&gt; ln, hn;</span><br><span class="line">            <span class="comment">// hash code不为负，代表这是条链表</span></span><br><span class="line">            <span class="keyword">if</span> (fh &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// fh &amp; n 获得hash code的新增有效位，用于将链表分离成两类</span></span><br><span class="line">                <span class="comment">// 要么是0要么是1，关于这个位运算的更多细节</span></span><br><span class="line">                <span class="comment">// 请看本文中有关HashMap扩容操作的解释</span></span><br><span class="line">                <span class="keyword">int</span> runBit = fh &amp; n;</span><br><span class="line">                Node&lt;K,V&gt; lastRun = f;</span><br><span class="line">                <span class="comment">// 这个循环用于记录最后一段连续的同一类节点</span></span><br><span class="line">                <span class="comment">// 这个类别是通过fh &amp; n来区分的</span></span><br><span class="line">                <span class="comment">// 这段连续的同类节点直接被复用，不会产生额外的复制</span></span><br><span class="line">                <span class="keyword">for</span> (Node&lt;K,V&gt; p = f.next; p != <span class="keyword">null</span>; p = p.next) &#123;</span><br><span class="line">                    <span class="keyword">int</span> b = p.hash &amp; n;</span><br><span class="line">                    <span class="keyword">if</span> (b != runBit) &#123;</span><br><span class="line">                        runBit = b;</span><br><span class="line">                        lastRun = p;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 0被放入ln链表，1被放入hn链表</span></span><br><span class="line">                <span class="comment">// lastRun是连续同类节点的起始节点</span></span><br><span class="line">                <span class="keyword">if</span> (runBit == <span class="number">0</span>) &#123;</span><br><span class="line">                    ln = lastRun;</span><br><span class="line">                    hn = <span class="keyword">null</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">else</span> &#123;</span><br><span class="line">                    hn = lastRun;</span><br><span class="line">                    ln = <span class="keyword">null</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 将最后一段的连续同类节点之前的节点按类别复制到ln或hn</span></span><br><span class="line">                <span class="comment">// 链表的插入方向是往头部插入的，Node构造函数的第四个参数是next</span></span><br><span class="line">                <span class="comment">// 所以就算遇到类别与lastRun一致的节点也只会被插入到头部</span></span><br><span class="line">                <span class="keyword">for</span> (Node&lt;K,V&gt; p = f; p != lastRun; p = p.next) &#123;</span><br><span class="line">                    <span class="keyword">int</span> ph = p.hash; K pk = p.key; V pv = p.val;</span><br><span class="line">                    <span class="keyword">if</span> ((ph &amp; n) == <span class="number">0</span>)</span><br><span class="line">                        ln = <span class="keyword">new</span> Node&lt;K,V&gt;(ph, pk, pv, ln);</span><br><span class="line">                    <span class="keyword">else</span></span><br><span class="line">                        hn = <span class="keyword">new</span> Node&lt;K,V&gt;(ph, pk, pv, hn);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// ln链表被放入到原索引位置，hn放入到原索引 + 旧数组容量</span></span><br><span class="line">                <span class="comment">// 这一点与HashMap一致，如果看不懂请去参考本文对HashMap扩容的讲解</span></span><br><span class="line">                setTabAt(nextTab, i, ln);</span><br><span class="line">                setTabAt(nextTab, i + n, hn);</span><br><span class="line">                setTabAt(tab, i, fwd); <span class="comment">// 标记该bucket已被处理</span></span><br><span class="line">                advance = <span class="keyword">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 对红黑树的操作，逻辑与链表一样，按新增有效位进行分类</span></span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (f <span class="keyword">instanceof</span> TreeBin) &#123;</span><br><span class="line">                TreeBin&lt;K,V&gt; t = (TreeBin&lt;K,V&gt;)f;</span><br><span class="line">                TreeNode&lt;K,V&gt; lo = <span class="keyword">null</span>, loTail = <span class="keyword">null</span>;</span><br><span class="line">                TreeNode&lt;K,V&gt; hi = <span class="keyword">null</span>, hiTail = <span class="keyword">null</span>;</span><br><span class="line">                <span class="keyword">int</span> lc = <span class="number">0</span>, hc = <span class="number">0</span>;</span><br><span class="line">                <span class="keyword">for</span> (Node&lt;K,V&gt; e = t.first; e != <span class="keyword">null</span>; e = e.next) &#123;</span><br><span class="line">                    <span class="keyword">int</span> h = e.hash;</span><br><span class="line">                    TreeNode&lt;K,V&gt; p = <span class="keyword">new</span> TreeNode&lt;K,V&gt;</span><br><span class="line">                        (h, e.key, e.val, <span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line">                    <span class="keyword">if</span> ((h &amp; n) == <span class="number">0</span>) &#123;</span><br><span class="line">                        <span class="keyword">if</span> ((p.prev = loTail) == <span class="keyword">null</span>)</span><br><span class="line">                            lo = p;</span><br><span class="line">                        <span class="keyword">else</span></span><br><span class="line">                            loTail.next = p;</span><br><span class="line">                        loTail = p;</span><br><span class="line">                        ++lc;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="keyword">if</span> ((p.prev = hiTail) == <span class="keyword">null</span>)</span><br><span class="line">                            hi = p;</span><br><span class="line">                        <span class="keyword">else</span></span><br><span class="line">                            hiTail.next = p;</span><br><span class="line">                        hiTail = p;</span><br><span class="line">                        ++hc;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 元素数量没有超过UNTREEIFY_THRESHOLD，退化成链表</span></span><br><span class="line">                ln = (lc &lt;= UNTREEIFY_THRESHOLD) ? untreeify(lo) :</span><br><span class="line">                    (hc != <span class="number">0</span>) ? <span class="keyword">new</span> TreeBin&lt;K,V&gt;(lo) : t;</span><br><span class="line">                hn = (hc &lt;= UNTREEIFY_THRESHOLD) ? untreeify(hi) :</span><br><span class="line">                    (lc != <span class="number">0</span>) ? <span class="keyword">new</span> TreeBin&lt;K,V&gt;(hi) : t;</span><br><span class="line">                setTabAt(nextTab, i, ln);</span><br><span class="line">                setTabAt(nextTab, i + n, hn);</span><br><span class="line">                setTabAt(tab, i, fwd);</span><br><span class="line">                advance = <span class="keyword">true</span>;</span><br><span class="line">            &#125;</span><br></pre></td></tr></table></figure>
<h4 id="计数"><a href="#计数" class="headerlink" title="计数"></a>计数</h4><hr>
<p>在Java 7中ConcurrentHashMap对每个Segment单独计数，想要得到总数就需要获得所有Segment的锁，然后进行统计。由于Java 8抛弃了Segment，显然是不能再这样做了，而且这种方法虽然简单准确但也舍弃了性能。</p>
<p>Java 8声明了一个<code>volatile</code>变量baseCount用于记录元素的个数，对这个变量的修改操作是基于CAS的，每当插入元素或删除元素时都会调用<code>addCount()</code>函数进行计数。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> <span class="keyword">long</span> baseCount;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">addCount</span><span class="params">(<span class="keyword">long</span> x, <span class="keyword">int</span> check)</span> </span>&#123;</span><br><span class="line">    CounterCell[] as; <span class="keyword">long</span> b, s;</span><br><span class="line">    <span class="comment">// 尝试使用CAS更新baseCount失败</span></span><br><span class="line">    <span class="comment">// 转用CounterCells进行更新</span></span><br><span class="line">    <span class="keyword">if</span> ((as = counterCells) != <span class="keyword">null</span> ||</span><br><span class="line">        !U.compareAndSwapLong(<span class="keyword">this</span>, BASECOUNT, b = baseCount, s = b + x)) &#123;</span><br><span class="line">        CounterCell a; <span class="keyword">long</span> v; <span class="keyword">int</span> m;</span><br><span class="line">        <span class="keyword">boolean</span> uncontended = <span class="keyword">true</span>;</span><br><span class="line">        <span class="comment">// 在CounterCells未初始化</span></span><br><span class="line">        <span class="comment">// 或尝试通过CAS更新当前线程的CounterCell失败时</span></span><br><span class="line">        <span class="comment">// 调用fullAddCount()，该函数负责初始化CounterCells和更新计数</span></span><br><span class="line">        <span class="keyword">if</span> (as == <span class="keyword">null</span> || (m = as.length - <span class="number">1</span>) &lt; <span class="number">0</span> ||</span><br><span class="line">            (a = as[ThreadLocalRandom.getProbe() &amp; m]) == <span class="keyword">null</span> ||</span><br><span class="line">            !(uncontended =</span><br><span class="line">              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) &#123;</span><br><span class="line">            fullAddCount(x, uncontended);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (check &lt;= <span class="number">1</span>)</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        <span class="comment">// 统计总数</span></span><br><span class="line">        s = sumCount();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (check &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">    	<span class="comment">// 判断是否需要扩容，在上文中已经讲过了</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;    </span><br></pre></td></tr></table></figure>
<p>counterCells是一个元素为CounterCell的数组，该数组的大小与当前机器的CPU数量有关，并且它不会被主动初始化，只有在调用<code>fullAddCount()</code>函数时才会进行初始化。</p>
<p>CounterCell是一个简单的内部静态类，每个CounterCell都是一个用于记录数量的单元：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Table of counter cells. When non-null, size is a power of 2.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> CounterCell[] counterCells;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * A padded cell for distributing counts.  Adapted from LongAdder</span></span><br><span class="line"><span class="comment"> * and Striped64.  See their internal docs for explanation.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@sun</span>.misc.Contended <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CounterCell</span> </span>&#123;</span><br><span class="line">    <span class="keyword">volatile</span> <span class="keyword">long</span> value;</span><br><span class="line">    CounterCell(<span class="keyword">long</span> x) &#123; value = x; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>注解<code>@sun.misc.Contended</code>用于解决伪共享问题。所谓伪共享，即是在同一缓存行（CPU缓存的基本单位）中存储了多个变量，当其中一个变量被修改时，就会影响到同一缓存行内的其他变量，导致它们也要跟着被标记为失效，其他变量的缓存命中率将会受到影响。解决伪共享问题的方法一般是对该变量填充一些无意义的占位数据，从而使它独享一个缓存行。</p>
<p>ConcurrentHashMap的计数设计与LongAdder类似。在一个低并发的情况下，就只是简单地使用CAS操作来对baseCount进行更新，但只要这个CAS操作失败一次，就代表有多个线程正在竞争，那么就转而使用CounterCell数组进行计数，数组内的每个ConuterCell都是一个独立的计数单元。</p>
<p>每个线程都会通过<code>ThreadLocalRandom.getProbe() &amp; m</code>寻址找到属于它的CounterCell，然后进行计数。ThreadLocalRandom是一个线程私有的伪随机数生成器，每个线程的probe都是不同的（这点基于ThreadLocalRandom的内部实现，它在内部维护了一个probeGenerator，这是一个类型为AtomicInteger的静态常量，每当初始化一个ThreadLocalRandom时probeGenerator都会先自增一个常量然后返回的整数即为当前线程的probe，probe变量被维护在Thread对象中），可以认为每个线程的probe就是它在CounterCell数组中的hash code。</p>
<p>这种方法将竞争数据按照线程的粒度进行分离，相比所有竞争线程对一个共享变量使用CAS不断尝试在性能上要效率多了，这也是为什么在高并发环境下LongAdder要优于AtomicInteger的原因。</p>
<p><code>fullAddCount()</code>函数根据当前线程的probe寻找对应的CounterCell进行计数，如果CounterCell数组未被初始化，则初始化CounterCell数组和CounterCell。该函数的实现与Striped64类（LongAdder的父类）的<code>longAccumulate()</code>函数是一样的，把CounterCell数组当成一个散列表，每个线程的probe就是hash code，散列函数也仅仅是简单的<code>(n - 1) &amp; probe</code>。</p>
<p>CounterCell数组的大小永远是一个2的n次方，初始容量为2，每次扩容的新容量都是之前容量乘以二，处于性能考虑，它的最大容量上限是机器的CPU数量。</p>
<p>所以说CounterCell数组的碰撞冲突是很严重的，因为它的bucket基数太小了。而发生碰撞就代表着一个CounterCell会被多个线程竞争，为了解决这个问题，Doug Lea使用无限循环加上CAS来模拟出一个自旋锁来保证线程安全，自旋锁的实现基于一个被<code>volatile</code>修饰的整数变量，该变量只会有两种状态：0和1，当它被设置为0时表示没有加锁，当它被设置为1时表示已被其他线程加锁。这个自旋锁用于保护初始化CounterCell、初始化CounterCell数组以及对CounterCell数组进行扩容时的安全。</p>
<p>CounterCell更新计数是依赖于CAS的，每次循环都会尝试通过CAS进行更新，如果成功就退出无限循环，否则就调用<code>ThreadLocalRandom.advanceProbe()</code>函数为当前线程更新probe，然后重新开始循环，以期望下一次寻址到的CounterCell没有被其他线程竞争。</p>
<p>如果连着两次CAS更新都没有成功，那么会对CounterCell数组进行一次扩容，这个扩容操作只会在当前循环中触发一次，而且只能在容量小于上限时触发。</p>
<p><code>fullAddCount()</code>函数的主要流程如下：</p>
<ul>
<li><p>首先检查当前线程有没有初始化过ThreadLocalRandom，如果没有则进行初始化。ThreadLocalRandom负责更新线程的probe，而probe又是在数组中进行寻址的关键。</p>
</li>
<li><p>检查CounterCell数组是否已经初始化，如果已初始化，那么就根据probe找到对应的CounterCell。</p>
<ul>
<li><p>如果这个CounterCell等于null，需要先初始化CounterCell，通过把计数增量传入构造函数，所以初始化只要成功就说明更新计数已经完成了。初始化的过程需要获取自旋锁。</p>
</li>
<li><p>如果不为null，就按上文所说的逻辑对CounterCell实施更新计数。</p>
</li>
</ul>
</li>
<li><p>CounterCell数组未被初始化，尝试获取自旋锁，进行初始化。数组初始化的过程会附带初始化一个CounterCell来记录计数增量，所以只要初始化成功就表示更新计数完成。</p>
</li>
<li><p>如果自旋锁被其他线程占用，无法进行数组的初始化，只好通过CAS更新baseCount。</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">fullAddCount</span><span class="params">(<span class="keyword">long</span> x, <span class="keyword">boolean</span> wasUncontended)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> h;</span><br><span class="line">    <span class="comment">// 当前线程的probe等于0，证明该线程的ThreadLocalRandom还未被初始化</span></span><br><span class="line">    <span class="comment">// 以及当前线程是第一次进入该函数</span></span><br><span class="line">    <span class="keyword">if</span> ((h = ThreadLocalRandom.getProbe()) == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 初始化ThreadLocalRandom，当前线程会被设置一个probe</span></span><br><span class="line">        ThreadLocalRandom.localInit();      <span class="comment">// force initialization</span></span><br><span class="line">        <span class="comment">// probe用于在CounterCell数组中寻址</span></span><br><span class="line">        h = ThreadLocalRandom.getProbe();</span><br><span class="line">        <span class="comment">// 未竞争标志</span></span><br><span class="line">        wasUncontended = <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 冲突标志</span></span><br><span class="line">    <span class="keyword">boolean</span> collide = <span class="keyword">false</span>;                <span class="comment">// True if last slot nonempty</span></span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        CounterCell[] as; CounterCell a; <span class="keyword">int</span> n; <span class="keyword">long</span> v;</span><br><span class="line">        <span class="comment">// CounterCell数组已初始化</span></span><br><span class="line">        <span class="keyword">if</span> ((as = counterCells) != <span class="keyword">null</span> &amp;&amp; (n = as.length) &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 如果寻址到的Cell为空，那么创建一个新的Cell</span></span><br><span class="line">            <span class="keyword">if</span> ((a = as[(n - <span class="number">1</span>) &amp; h]) == <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="comment">// cellsBusy是一个只有0和1两个状态的volatile整数</span></span><br><span class="line">                <span class="comment">// 它被当做一个自旋锁，0代表无锁，1代表加锁</span></span><br><span class="line">                <span class="keyword">if</span> (cellsBusy == <span class="number">0</span>) &#123;            <span class="comment">// Try to attach new Cell</span></span><br><span class="line">                    <span class="comment">// 将传入的x作为初始值创建一个新的CounterCell</span></span><br><span class="line">                    CounterCell r = <span class="keyword">new</span> CounterCell(x); <span class="comment">// Optimistic create</span></span><br><span class="line">                    <span class="comment">// 通过CAS尝试对自旋锁加锁</span></span><br><span class="line">                    <span class="keyword">if</span> (cellsBusy == <span class="number">0</span> &amp;&amp;</span><br><span class="line">                        U.compareAndSwapInt(<span class="keyword">this</span>, CELLSBUSY, <span class="number">0</span>, <span class="number">1</span>)) &#123;</span><br><span class="line">                        <span class="comment">// 加锁成功，声明Cell是否创建成功的标志</span></span><br><span class="line">                        <span class="keyword">boolean</span> created = <span class="keyword">false</span>;</span><br><span class="line">                        <span class="keyword">try</span> &#123;               <span class="comment">// Recheck under lock</span></span><br><span class="line">                            CounterCell[] rs; <span class="keyword">int</span> m, j;</span><br><span class="line">                            <span class="comment">// 再次检查CounterCell数组是否不为空</span></span><br><span class="line">                            <span class="comment">// 并且寻址到的Cell为空</span></span><br><span class="line">                            <span class="keyword">if</span> ((rs = counterCells) != <span class="keyword">null</span> &amp;&amp;</span><br><span class="line">                                (m = rs.length) &gt; <span class="number">0</span> &amp;&amp;</span><br><span class="line">                                rs[j = (m - <span class="number">1</span>) &amp; h] == <span class="keyword">null</span>) &#123;</span><br><span class="line">                                <span class="comment">// 将之前创建的新Cell放入数组</span></span><br><span class="line">                                rs[j] = r;</span><br><span class="line">                                created = <span class="keyword">true</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                            <span class="comment">// 释放锁</span></span><br><span class="line">                            cellsBusy = <span class="number">0</span>;</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="comment">// 如果已经创建成功，中断循环</span></span><br><span class="line">                        <span class="comment">// 因为新Cell的初始值就是传入的增量，所以计数已经完毕了</span></span><br><span class="line">                        <span class="keyword">if</span> (created)</span><br><span class="line">                            <span class="keyword">break</span>;</span><br><span class="line">                        <span class="comment">// 如果未成功</span></span><br><span class="line">                        <span class="comment">// 代表as[(n - 1) &amp; h]这个位置的Cell已经被其他线程设置</span></span><br><span class="line">                        <span class="comment">// 那么就从循环头重新开始</span></span><br><span class="line">                        <span class="keyword">continue</span>;           <span class="comment">// Slot is now non-empty</span></span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                collide = <span class="keyword">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// as[(n - 1) &amp; h]非空</span></span><br><span class="line">            <span class="comment">// 在addCount()函数中通过CAS更新当前线程的Cell进行计数失败</span></span><br><span class="line">            <span class="comment">// 会传入wasUncontended = false，代表已经有其他线程进行竞争</span></span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (!wasUncontended)       <span class="comment">// CAS already known to fail</span></span><br><span class="line">                <span class="comment">// 设置未竞争标志，之后会重新计算probe，然后重新执行循环</span></span><br><span class="line">                wasUncontended = <span class="keyword">true</span>;      <span class="comment">// Continue after rehash</span></span><br><span class="line">            <span class="comment">// 尝试进行计数，如果成功，那么就退出循环</span></span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="comment">// 尝试更新失败，检查counterCell数组是否已经扩容</span></span><br><span class="line">            <span class="comment">// 或者容量达到最大值（CPU的数量）</span></span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (counterCells != as || n &gt;= NCPU)</span><br><span class="line">                <span class="comment">// 设置冲突标志，防止跳入下面的扩容分支</span></span><br><span class="line">                <span class="comment">// 之后会重新计算probe</span></span><br><span class="line">                collide = <span class="keyword">false</span>;            <span class="comment">// At max size or stale</span></span><br><span class="line">            <span class="comment">// 设置冲突标志，重新执行循环</span></span><br><span class="line">            <span class="comment">// 如果下次循环执行到该分支，并且冲突标志仍然为true</span></span><br><span class="line">            <span class="comment">// 那么会跳过该分支，到下一个分支进行扩容</span></span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (!collide)</span><br><span class="line">                collide = <span class="keyword">true</span>;</span><br><span class="line">            <span class="comment">// 尝试加锁，然后对counterCells数组进行扩容</span></span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (cellsBusy == <span class="number">0</span> &amp;&amp;</span><br><span class="line">                     U.compareAndSwapInt(<span class="keyword">this</span>, CELLSBUSY, <span class="number">0</span>, <span class="number">1</span>)) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">// 检查是否已被扩容</span></span><br><span class="line">                    <span class="keyword">if</span> (counterCells == as) &#123;<span class="comment">// Expand table unless stale</span></span><br><span class="line">                        <span class="comment">// 新数组容量为之前的1倍</span></span><br><span class="line">                        CounterCell[] rs = <span class="keyword">new</span> CounterCell[n &lt;&lt; <span class="number">1</span>];</span><br><span class="line">                        <span class="comment">// 迁移数据到新数组</span></span><br><span class="line">                        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; n; ++i)</span><br><span class="line">                            rs[i] = as[i];</span><br><span class="line">                        counterCells = rs;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    <span class="comment">// 释放锁</span></span><br><span class="line">                    cellsBusy = <span class="number">0</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                collide = <span class="keyword">false</span>;</span><br><span class="line">                <span class="comment">// 重新执行循环</span></span><br><span class="line">                <span class="keyword">continue</span>;                   <span class="comment">// Retry with expanded table</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 为当前线程重新计算probe</span></span><br><span class="line">            h = ThreadLocalRandom.advanceProbe(h);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// CounterCell数组未初始化，尝试获取自旋锁，然后进行初始化</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (cellsBusy == <span class="number">0</span> &amp;&amp; counterCells == as &amp;&amp;</span><br><span class="line">                 U.compareAndSwapInt(<span class="keyword">this</span>, CELLSBUSY, <span class="number">0</span>, <span class="number">1</span>)) &#123;</span><br><span class="line">            <span class="keyword">boolean</span> init = <span class="keyword">false</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;                           <span class="comment">// Initialize table</span></span><br><span class="line">                <span class="keyword">if</span> (counterCells == as) &#123;</span><br><span class="line">                    <span class="comment">// 初始化CounterCell数组，初始容量为2</span></span><br><span class="line">                    CounterCell[] rs = <span class="keyword">new</span> CounterCell[<span class="number">2</span>];</span><br><span class="line">                    <span class="comment">// 初始化CounterCell</span></span><br><span class="line">                    rs[h &amp; <span class="number">1</span>] = <span class="keyword">new</span> CounterCell(x);</span><br><span class="line">                    counterCells = rs;</span><br><span class="line">                    init = <span class="keyword">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                cellsBusy = <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 初始化CounterCell数组成功，退出循环</span></span><br><span class="line">            <span class="keyword">if</span> (init)</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 如果自旋锁被占用，则只好尝试更新baseCount</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapLong(<span class="keyword">this</span>, BASECOUNT, v = baseCount, v + x))</span><br><span class="line">            <span class="keyword">break</span>;                          <span class="comment">// Fall back on using base</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>对于统计总数，只要能够理解CounterCell的思想，就很简单了。仔细想一想，每次计数的更新都会被分摊在baseCount和CounterCell数组中的某一CounterCell，想要获得总数，把它们统计相加就是了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">size</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">long</span> n = sumCount();</span><br><span class="line">    <span class="keyword">return</span> ((n &lt; <span class="number">0L</span>) ? <span class="number">0</span> :</span><br><span class="line">            (n &gt; (<span class="keyword">long</span>)Integer.MAX_VALUE) ? Integer.MAX_VALUE :</span><br><span class="line">            (<span class="keyword">int</span>)n);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">final</span> <span class="keyword">long</span> <span class="title">sumCount</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    CounterCell[] as = counterCells; CounterCell a;</span><br><span class="line">    <span class="keyword">long</span> sum = baseCount;</span><br><span class="line">    <span class="keyword">if</span> (as != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; as.length; ++i) &#123;</span><br><span class="line">            <span class="keyword">if</span> ((a = as[i]) != <span class="keyword">null</span>)</span><br><span class="line">                sum += a.value;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> sum;</span><br><span class="line">&#125;   </span><br></pre></td></tr></table></figure>
<p>其实<code>size()</code>函数返回的总数可能并不是百分百精确的，试想如果前一个遍历过的CounterCell又进行了更新会怎么样？尽管只是一个估算值，但在大多数场景下都还能接受，而且性能上是要比Java 7好上太多了。</p>
<h4 id="添加元素-1"><a href="#添加元素-1" class="headerlink" title="添加元素"></a>添加元素</h4><hr>
<p>添加元素的主要逻辑与HashMap没什么区别，有所区别的复杂操作如扩容和计数我们上文都已经深入解析过了，所以整体来说<code>putVal()</code>函数还是比较简单的，可能唯一需要注意的就是在对节点进行操作的时候需要通过互斥锁保证线程安全，这个互斥锁的粒度很小，只对需要操作的这个bucket加锁。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> putVal(key, value, <span class="keyword">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Implementation for put and putIfAbsent */</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">putVal</span><span class="params">(K key, V value, <span class="keyword">boolean</span> onlyIfAbsent)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (key == <span class="keyword">null</span> || value == <span class="keyword">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line">    <span class="keyword">int</span> hash = spread(key.hashCode());</span><br><span class="line">    <span class="keyword">int</span> binCount = <span class="number">0</span>; <span class="comment">// 节点计数器，用于判断是否需要树化</span></span><br><span class="line">    <span class="comment">// 无限循环+CAS，无锁的标准套路</span></span><br><span class="line">    <span class="keyword">for</span> (Node&lt;K,V&gt;[] tab = table;;) &#123;</span><br><span class="line">        Node&lt;K,V&gt; f; <span class="keyword">int</span> n, i, fh;</span><br><span class="line">        <span class="comment">// 初始化table</span></span><br><span class="line">        <span class="keyword">if</span> (tab == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line">            tab = initTable();</span><br><span class="line">        <span class="comment">// bucket为null，通过CAS创建头节点，如果成功就结束循环</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((f = tabAt(tab, i = (n - <span class="number">1</span>) &amp; hash)) == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (casTabAt(tab, i, <span class="keyword">null</span>,</span><br><span class="line">                         <span class="keyword">new</span> Node&lt;K,V&gt;(hash, key, value, <span class="keyword">null</span>)))</span><br><span class="line">                <span class="keyword">break</span>;                   <span class="comment">// no lock when adding to empty bin</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// bucket为ForwardingNode</span></span><br><span class="line">        <span class="comment">// 当前线程前去协助进行扩容</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((fh = f.hash) == MOVED)</span><br><span class="line">            tab = helpTransfer(tab, f);</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            V oldVal = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">synchronized</span> (f) &#123;</span><br><span class="line">                <span class="keyword">if</span> (tabAt(tab, i) == f) &#123;</span><br><span class="line">                    <span class="comment">// 节点是链表</span></span><br><span class="line">                    <span class="keyword">if</span> (fh &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                        binCount = <span class="number">1</span>;</span><br><span class="line">                        <span class="keyword">for</span> (Node&lt;K,V&gt; e = f;; ++binCount) &#123;</span><br><span class="line">                            K ek;</span><br><span class="line">                            <span class="comment">// 找到目标，设置value</span></span><br><span class="line">                            <span class="keyword">if</span> (e.hash == hash &amp;&amp;</span><br><span class="line">                                ((ek = e.key) == key ||</span><br><span class="line">                                 (ek != <span class="keyword">null</span> &amp;&amp; key.equals(ek)))) &#123;</span><br><span class="line">                                oldVal = e.val;</span><br><span class="line">                                <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line">                                    e.val = value;</span><br><span class="line">                                <span class="keyword">break</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                            Node&lt;K,V&gt; pred = e;</span><br><span class="line">                            <span class="comment">// 未找到节点，插入新节点到链表尾部</span></span><br><span class="line">                            <span class="keyword">if</span> ((e = e.next) == <span class="keyword">null</span>) &#123;</span><br><span class="line">                                pred.next = <span class="keyword">new</span> Node&lt;K,V&gt;(hash, key,</span><br><span class="line">                                                          value, <span class="keyword">null</span>);</span><br><span class="line">                                <span class="keyword">break</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="comment">// 节点是红黑树</span></span><br><span class="line">                    <span class="keyword">else</span> <span class="keyword">if</span> (f <span class="keyword">instanceof</span> TreeBin) &#123;</span><br><span class="line">                        Node&lt;K,V&gt; p;</span><br><span class="line">                        binCount = <span class="number">2</span>;</span><br><span class="line">                        <span class="keyword">if</span> ((p = ((TreeBin&lt;K,V&gt;)f).putTreeVal(hash, key,</span><br><span class="line">                                                       value)) != <span class="keyword">null</span>) &#123;</span><br><span class="line">                            oldVal = p.val;</span><br><span class="line">                            <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line">                                p.val = value;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 根据bucket中的节点数决定是否树化</span></span><br><span class="line">            <span class="keyword">if</span> (binCount != <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (binCount &gt;= TREEIFY_THRESHOLD)</span><br><span class="line">                    treeifyBin(tab, i);</span><br><span class="line">                <span class="comment">// oldVal不等于null，说明没有新节点</span></span><br><span class="line">                <span class="comment">// 所以直接返回，不进行计数</span></span><br><span class="line">                <span class="keyword">if</span> (oldVal != <span class="keyword">null</span>)</span><br><span class="line">                    <span class="keyword">return</span> oldVal;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 计数</span></span><br><span class="line">    addCount(<span class="number">1L</span>, binCount);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>至于删除元素的操作位于函数<code>replaceNode(Object key, V value, Object cv)</code>，当<code>table[key].val</code>等于期望值cv时（或cv等于null），更新节点的值为value，如果value等于null，那么删除该节点。</p>
<p><code>remove()</code>函数通过调用<code>replaceNode(key, null, null)</code>来达成删除目标节点的目的，<code>replaceNode()</code>的具体实现与<code>putVal()</code>没什么差别，只不过对链表的操作有所不同而已，所以就不多叙述了。</p>
<h4 id="并行计算"><a href="#并行计算" class="headerlink" title="并行计算"></a>并行计算</h4><hr>
<p>Java 8除了对ConcurrentHashMap重新设计以外，还引入了基于Lambda表达式的Stream API。它是对集合对象功能上的增强（所以不止ConcurrentHashMap，其他集合也都实现了该API），以一种优雅的方式来批量操作、聚合或遍历集合中的数据。</p>
<p>最重要的是，它还提供了并行模式，充分利用了多核CPU的优势实现并行计算。让我们看看如下的示例代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">    ConcurrentHashMap&lt;String, Integer&gt; map = <span class="keyword">new</span> ConcurrentHashMap&lt;&gt;();</span><br><span class="line">    String keys = <span class="string">&quot;ABCDEFG&quot;</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= keys.length(); i++) &#123;</span><br><span class="line">        map.put(String.valueOf(keys.charAt(i - <span class="number">1</span>)), i);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    map.forEach(<span class="number">2</span>,</span><br><span class="line">            (k, v) -&gt; System.out.println(<span class="string">&quot;key-&quot;</span> + k + <span class="string">&quot;:value-&quot;</span> + v + <span class="string">&quot;. by thread-&gt;&quot;</span> + Thread.currentThread().getName()));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这段代码通过两个线程（包括主线程）并行地遍历map中的元素，然后输出到控制台，输出如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">key-A:value-1. by thread-&gt;main</span><br><span class="line">key-D:value-4. by thread-&gt;ForkJoinPool.commonPool-worker-2</span><br><span class="line">key-B:value-2. by thread-&gt;main</span><br><span class="line">key-E:value-5. by thread-&gt;ForkJoinPool.commonPool-worker-2</span><br><span class="line">key-C:value-3. by thread-&gt;main</span><br><span class="line">key-F:value-6. by thread-&gt;ForkJoinPool.commonPool-worker-2</span><br><span class="line">key-G:value-7. by thread-&gt;ForkJoinPool.commonPool-worker-2</span><br></pre></td></tr></table></figure>
<p>很明显，有两个线程在进行工作，那么这是怎么实现的呢？我们先来看看<code>forEach()</code>函数：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">forEach</span><span class="params">(<span class="keyword">long</span> parallelismThreshold,</span></span></span><br><span class="line"><span class="function"><span class="params">                    BiConsumer&lt;? <span class="keyword">super</span> K,? <span class="keyword">super</span> V&gt; action)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (action == <span class="keyword">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line">    <span class="keyword">new</span> ForEachMappingTask&lt;K,V&gt;</span><br><span class="line">        (<span class="keyword">null</span>, batchFor(parallelismThreshold), <span class="number">0</span>, <span class="number">0</span>, table,</span><br><span class="line">         action).invoke();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><code>parallelismThreshold</code>是需要并行执行该操作的线程数量，<code>action</code>则是回调函数（我们想要执行的操作）。<code>action</code>的类型为BiConsumer，是一个用于支持Lambda表达式的FunctionalInterface，它接受两个输入参数并返回0个结果。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@FunctionalInterface</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">BiConsumer</span>&lt;<span class="title">T</span>, <span class="title">U</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Performs this operation on the given arguments.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> t the first input argument</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> u the second input argument</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">accept</span><span class="params">(T t, U u)</span></span>;</span><br></pre></td></tr></table></figure>
<p>看来实现并行计算的关键在于ForEachMappingTask对象，通过它的继承关系结构图可以发现，ForEachMappingTask其实就是ForkJoinTask。</p>
<p><img src="http://wx3.sinaimg.cn/large/63503acbly1fpu3aa1nokj20a70bs3ym.jpg"></p>
<p>集合的并行计算是基于Fork/Join框架实现的，工作线程交由ForkJoinPool线程池维护。它推崇分而治之的思想，将一个大的任务分解成多个小的任务，通过<code>fork()</code>函数（有点像Linux的<code>fork()</code>系统调用来创建子进程）来开启一个工作线程执行其中一个小任务，通过<code>join()</code>函数等待工作线程执行完毕（需要等所有工作线程执行完毕才能合并最终结果），只要所有的小任务都已经处理完成，就代表这个大的任务也完成了。</p>
<p>像上文中的示例代码就是将遍历这个大任务分解成了N个小任务，然后交由两个工作线程进行处理。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">ForEachMappingTask</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;</span></span><br><span class="line"><span class="class">    <span class="keyword">extends</span> <span class="title">BulkTask</span>&lt;<span class="title">K</span>,<span class="title">V</span>,<span class="title">Void</span>&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> BiConsumer&lt;? <span class="keyword">super</span> K, ? <span class="keyword">super</span> V&gt; action;</span><br><span class="line">    ForEachMappingTask</span><br><span class="line">        (BulkTask&lt;K,V,?&gt; p, <span class="keyword">int</span> b, <span class="keyword">int</span> i, <span class="keyword">int</span> f, Node&lt;K,V&gt;[] t,</span><br><span class="line">         BiConsumer&lt;? <span class="keyword">super</span> K,? <span class="keyword">super</span> V&gt; action) &#123;</span><br><span class="line">        <span class="keyword">super</span>(p, b, i, f, t);</span><br><span class="line">        <span class="keyword">this</span>.action = action;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">compute</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> BiConsumer&lt;? <span class="keyword">super</span> K, ? <span class="keyword">super</span> V&gt; action;</span><br><span class="line">        <span class="keyword">if</span> ((action = <span class="keyword">this</span>.action) != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = baseIndex, f, h; batch &gt; <span class="number">0</span> &amp;&amp;</span><br><span class="line">                     (h = ((f = baseLimit) + i) &gt;&gt;&gt; <span class="number">1</span>) &gt; i;) &#123;</span><br><span class="line">                <span class="comment">// 记录待完成任务的数量</span></span><br><span class="line">                addToPendingCount(<span class="number">1</span>);</span><br><span class="line">                <span class="comment">// 开启一个工作线程执行任务</span></span><br><span class="line">                <span class="comment">// 其余参数是任务的区间以及table和回调函数</span></span><br><span class="line">                <span class="keyword">new</span> ForEachMappingTask&lt;K,V&gt;</span><br><span class="line">                    (<span class="keyword">this</span>, batch &gt;&gt;&gt;= <span class="number">1</span>, baseLimit = h, f, tab,</span><br><span class="line">                     action).fork();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">for</span> (Node&lt;K,V&gt; p; (p = advance()) != <span class="keyword">null</span>; )</span><br><span class="line">                <span class="comment">// 调用回调函数</span></span><br><span class="line">                action.accept(p.key, p.val);</span><br><span class="line">            <span class="comment">// 与addToPendingCount()相反</span></span><br><span class="line">            <span class="comment">// 它会减少待完成任务的计数器</span></span><br><span class="line">            <span class="comment">// 如果该计数器为0，代表所有任务已经完成了</span></span><br><span class="line">            propagateCompletion();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>其他并行计算函数的实现也都差不多，只不过具体的Task实现不同，例如<code>search()</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;U&gt; <span class="function">U <span class="title">search</span><span class="params">(<span class="keyword">long</span> parallelismThreshold,</span></span></span><br><span class="line"><span class="function"><span class="params">                    BiFunction&lt;? <span class="keyword">super</span> K, ? <span class="keyword">super</span> V, ? extends U&gt; searchFunction)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (searchFunction == <span class="keyword">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> SearchMappingsTask&lt;K,V,U&gt;</span><br><span class="line">        (<span class="keyword">null</span>, batchFor(parallelismThreshold), <span class="number">0</span>, <span class="number">0</span>, table,</span><br><span class="line">         searchFunction, <span class="keyword">new</span> AtomicReference&lt;U&gt;()).invoke();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>为了节省篇幅（说实话现在似乎很少有人能耐心看完一篇长文_(:з」∠)_），有关Stream API是如何使用Fork/Join框架进行工作以及实现细节就不多讲了，以后有机会再说吧。</p>
<h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h3><hr>
<ul>
<li><p><a target="_blank" rel="noopener" href="http://www.wikiwand.com/en/Associative_array">Associative array - Wikiwand</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="http://www.wikiwand.com/en/Hash_table">Hash table - Wikiwand</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="http://www.wikiwand.com/en/Hash_function">Hash function - Wikiwand</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html">ConcurrentHashMap (Java Platform SE 8 )</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAdder.html">LongAdder (Java Platform SE 8 )</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/">Java Magic. Part 4: sun.misc.Unsafe</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://dzone.com/articles/concurrenthashmap-in-java8">ConcurrentHashMap in Java 8 - DZone Java</a></p>
</li>
</ul>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh">
    <link itemprop="mainEntityOfPage" href="https://suyuhuan.gitee.io/yuwanzi.io/2018/01/08/2018-01-08-spring_boot_auto_configure/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/yuwanzi.io/images/avatar.gif">
      <meta itemprop="name" content="玉丸子">
      <meta itemprop="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="玉丸子 | Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/yuwanzi.io/2018/01/08/2018-01-08-spring_boot_auto_configure/" class="post-title-link" itemprop="url">Spring Boot自动配置的"魔法"是如何实现的？</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Veröffentlicht am</span>

      <time title="Erstellt: 2018-01-08 18:00:00" itemprop="dateCreated datePublished" datetime="2018-01-08T18:00:00+08:00">2018-01-08</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">Bearbeitet am</span>
        <time title="Geändert am: 2020-11-07 08:58:17" itemprop="dateModified" datetime="2020-11-07T08:58:17+08:00">2020-11-07</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">in</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%90%8E%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">后端</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%90%8E%E7%AB%AF/Java/" itemprop="url" rel="index"><span itemprop="name">Java</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%90%8E%E7%AB%AF/Java/Spring-Boot/" itemprop="url" rel="index"><span itemprop="name">Spring Boot</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <p>Spring Boot是Spring旗下众多的子项目之一，其理念是约定优于配置，它通过实现了自动配置（大多数用户平时习惯设置的配置作为默认配置）的功能来为用户快速构建出标准化的应用。Spring Boot的特点可以概述为如下几点：</p>
<ul>
<li><p>内置了嵌入式的Tomcat、Jetty等Servlet容器，应用可以不用打包成War格式，而是可以直接以Jar格式运行。</p>
</li>
<li><p>提供了多个可选择的”starter”以简化Maven的依赖管理（也支持Gradle），让您可以按需加载需要的功能模块。</p>
</li>
<li><p>尽可能地进行自动配置，减少了用户需要动手写的各种冗余配置项，Spring Boot提倡无XML配置文件的理念，使用Spring Boot生成的应用完全不会生成任何配置代码与XML配置文件。</p>
</li>
<li><p>提供了一整套的对应用状态的监控与管理的功能模块（通过引入spring-boot-starter-actuator），包括应用的线程信息、内存信息、应用是否处于健康状态等，为了满足更多的资源监控需求，Spring Cloud中的很多模块还对其进行了扩展。</p>
</li>
</ul>
<p>有关Spring Boot的使用方法就不做多介绍了，如有兴趣请自行阅读官方文档<a target="_blank" rel="noopener" href="https://projects.spring.io/spring-boot/">Spring Boot</a>或其他文章。</p>
<p>如今微服务的概念愈来愈热，转型或尝试微服务的团队也在如日渐增，而对于技术选型，Spring Cloud是一个比较好的选择，它提供了一站式的分布式系统解决方案，包含了许多构建分布式系统与微服务需要用到的组件，例如服务治理、API网关、配置中心、消息总线以及容错管理等模块。可以说，Spring Cloud”全家桶”极其适合刚刚接触微服务的团队。似乎有点跑题了，不过说了这么多，我想要强调的是，Spring Cloud中的每个组件都是基于Spring Boot构建的，而理解了Spring Boot的自动配置的原理，显然也是有好处的。</p>
<p>Spring Boot的自动配置看起来神奇，其实原理非常简单，背后全依赖于@Conditional注解来实现的。</p>
<blockquote>
<p>本文作者为<a target="_blank" rel="noopener" href="https://github.com/SylvanasSun">SylvanasSun(sylvanas.sun@gmail.com)</a>，首发于<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/">SylvanasSun’s Blog</a>。<br>原文链接：<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/2018/01/08/2018-01-08-spring_boot_auto_configure/">https://sylvanassun.github.io/2018/01/08/2018-01-08-spring_boot_auto_configure/</a><br>（转载请务必保留本段声明，并且保留超链接。）</p>
</blockquote>
<h3 id="什么是-Conditional？"><a href="#什么是-Conditional？" class="headerlink" title="什么是@Conditional？"></a>什么是@Conditional？</h3><hr>
<p>@Conditional是由Spring 4提供的一个新特性，用于根据特定条件来控制Bean的创建行为。而在我们开发基于Spring的应用的时候，难免会需要根据条件来注册Bean。</p>
<p>例如，你想要根据不同的运行环境，来让Spring注册对应环境的数据源Bean，对于这种简单的情况，完全可以使用@Profile注解实现，就像下面代码所示：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AppConfig</span> </span>&#123;</span><br><span class="line">	<span class="meta">@Bean</span></span><br><span class="line">	<span class="meta">@Profile(&quot;DEV&quot;)</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> DataSource <span class="title">devDataSource</span><span class="params">()</span> </span>&#123;</span><br><span class="line">		...</span><br><span class="line">	&#125;</span><br><span class="line">	</span><br><span class="line">	<span class="meta">@Bean</span></span><br><span class="line">	<span class="meta">@Profile(&quot;PROD&quot;)</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> DataSource <span class="title">prodDataSource</span><span class="params">()</span> </span>&#123;</span><br><span class="line">		...</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>剩下只需要设置对应的Profile属性即可，设置方法有如下三种：</p>
<ul>
<li><p>通过<code>context.getEnvironment().setActiveProfiles(&quot;PROD&quot;)</code>来设置Profile属性。</p>
</li>
<li><p>通过设定jvm的<code>spring.profiles.active</code>参数来设置环境（Spring Boot中可以直接在<code>application.properties</code>配置文件中设置该属性）。</p>
</li>
<li><p>通过在DispatcherServlet的初始参数中设置。</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;servlet&gt;</span><br><span class="line">	&lt;servlet-name&gt;dispatcher&lt;/servlet-name&gt;</span><br><span class="line">	&lt;servlet-<span class="class"><span class="keyword">class</span>&gt;<span class="title">org</span>.<span class="title">springframework</span>.<span class="title">web</span>.<span class="title">servlet</span>.<span class="title">DispatcherServlet</span>&lt;/<span class="title">servlet</span>-<span class="title">class</span>&gt;</span></span><br><span class="line"><span class="class">	&lt;<span class="title">init</span>-<span class="title">param</span>&gt;</span></span><br><span class="line"><span class="class">		&lt;<span class="title">param</span>-<span class="title">name</span>&gt;<span class="title">spring</span>.<span class="title">profiles</span>.<span class="title">active</span>&lt;/<span class="title">param</span>-<span class="title">name</span>&gt;</span></span><br><span class="line"><span class="class">		&lt;<span class="title">param</span>-<span class="title">value</span>&gt;<span class="title">PROD</span>&lt;/<span class="title">param</span>-<span class="title">value</span>&gt;</span></span><br><span class="line"><span class="class">	&lt;/<span class="title">init</span>-<span class="title">param</span>&gt;</span></span><br><span class="line"><span class="class">&lt;/<span class="title">servlet</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>但这种方法只局限于简单的情况，而且通过源码我们可以发现@Profile自身也使用了@Conditional注解。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.springframework.context.annotation;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Target(&#123;ElementType.TYPE, ElementType.METHOD&#125;)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Conditional(&#123;ProfileCondition.class&#125;)</span> <span class="comment">// 组合了Conditional注解</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Profile &#123;</span><br><span class="line">    String[] value();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> org.springframework.context.annotation;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ProfileCondition</span> <span class="keyword">implements</span> <span class="title">Condition</span> </span>&#123;</span><br><span class="line">    ProfileCondition() &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 通过提取出@Profile注解中的value值来与profiles配置信息进行匹配</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(ConditionContext context, AnnotatedTypeMetadata metadata)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(context.getEnvironment() != <span class="keyword">null</span>) &#123;</span><br><span class="line">            MultiValueMap attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());</span><br><span class="line">            <span class="keyword">if</span>(attrs != <span class="keyword">null</span>) &#123;</span><br><span class="line">                Iterator var4 = ((List)attrs.get(<span class="string">&quot;value&quot;</span>)).iterator();</span><br><span class="line"></span><br><span class="line">                Object value;</span><br><span class="line">                <span class="keyword">do</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span>(!var4.hasNext()) &#123;</span><br><span class="line">                        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    value = var4.next();</span><br><span class="line">                &#125; <span class="keyword">while</span>(!context.getEnvironment().acceptsProfiles((String[])((String[])value)));</span><br><span class="line"></span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>在业务复杂的情况下，显然需要使用到@Conditional注解来提供更加灵活的条件判断，例如以下几个判断条件：</p>
<ul>
<li><p>在类路径中是否存在这样的一个类。</p>
</li>
<li><p>在Spring容器中是否已经注册了某种类型的Bean（如未注册，我们可以让其自动注册到容器中，上一条同理）。</p>
</li>
<li><p>一个文件是否在特定的位置上。</p>
</li>
<li><p>一个特定的系统属性是否存在。</p>
</li>
<li><p>在Spring的配置文件中是否设置了某个特定的值。</p>
</li>
</ul>
<p>举个栗子，假设我们有两个基于不同数据库实现的DAO，它们全都实现了UserDao，其中JdbcUserDAO与MySql进行连接，MongoUserDAO与MongoDB进行连接。现在，我们有了一个需求，需要根据命令行传入的系统参数来注册对应的UserDao，就像<code>java -jar app.jar -DdbType=MySQL</code>会注册JdbcUserDao，而<code>java -jar app.jar -DdbType=MongoDB</code>则会注册MongoUserDao。使用@Conditional可以很轻松地实现这个功能，仅仅需要在你自定义的条件类中去实现Condition接口，让我们来看下面的代码。（以下案例来自：<a target="_blank" rel="noopener" href="https://dzone.com/articles/how-springboot-autoconfiguration-magic-works%EF%BC%89">https://dzone.com/articles/how-springboot-autoconfiguration-magic-works）</a></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserDAO</span> </span>&#123;</span><br><span class="line">	....</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdbcUserDAO</span> <span class="keyword">implements</span> <span class="title">UserDAO</span> </span>&#123;</span><br><span class="line">	....</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MongoUserDAO</span> <span class="keyword">implements</span> <span class="title">UserDAO</span> </span>&#123;</span><br><span class="line">	....</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MySQLDatabaseTypeCondition</span> <span class="keyword">implements</span> <span class="title">Condition</span> </span>&#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)</span> </span>&#123;</span><br><span class="line"> 		String enabledDBType = System.getProperty(<span class="string">&quot;dbType&quot;</span>); <span class="comment">// 获得系统参数 dbType</span></span><br><span class="line"> 		<span class="comment">// 如果该值等于MySql，则条件成立</span></span><br><span class="line"> 		<span class="keyword">return</span> (enabledDBType != <span class="keyword">null</span> &amp;&amp; enabledDBType.equalsIgnoreCase(<span class="string">&quot;MySql&quot;</span>));</span><br><span class="line"> 	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 与上述逻辑一致</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MongoDBDatabaseTypeCondition</span> <span class="keyword">implements</span> <span class="title">Condition</span> </span>&#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)</span> </span>&#123;</span><br><span class="line"> 		String enabledDBType = System.getProperty(<span class="string">&quot;dbType&quot;</span>);</span><br><span class="line"> 		<span class="keyword">return</span> (enabledDBType != <span class="keyword">null</span> &amp;&amp; enabledDBType.equalsIgnoreCase(<span class="string">&quot;MongoDB&quot;</span>));</span><br><span class="line"> 	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据条件来注册不同的Bean</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AppConfig</span> </span>&#123;</span><br><span class="line">	<span class="meta">@Bean</span></span><br><span class="line">	<span class="meta">@Conditional(MySQLDatabaseTypeCondition.class)</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> UserDAO <span class="title">jdbcUserDAO</span><span class="params">()</span> </span>&#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="keyword">new</span> JdbcUserDAO();</span><br><span class="line">	&#125;</span><br><span class="line">	</span><br><span class="line">	<span class="meta">@Bean</span></span><br><span class="line">	<span class="meta">@Conditional(MongoDBDatabaseTypeCondition.class)</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> UserDAO <span class="title">mongoUserDAO</span><span class="params">()</span> </span>&#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="keyword">new</span> MongoUserDAO();</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>现在，我们又有了一个新需求，我们想要根据当前工程的类路径中是否存在MongoDB的驱动类来确认是否注册MongoUserDAO。为了实现这个需求，可以创建检查MongoDB驱动是否存在的两个条件类。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MongoDriverPresentsCondition</span> <span class="keyword">implements</span> <span class="title">Condition</span> </span>&#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)</span> </span>&#123;</span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			Class.forName(<span class="string">&quot;com.mongodb.Server&quot;</span>);</span><br><span class="line">			<span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">		&#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">			<span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MongoDriverNotPresentsCondition</span> <span class="keyword">implements</span> <span class="title">Condition</span> </span>&#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)</span> </span>&#123;</span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			Class.forName(<span class="string">&quot;com.mongodb.Server&quot;</span>);</span><br><span class="line">			<span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">		&#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">			<span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>假如，你想要在UserDAO没有被注册的情况下去注册一个UserDAOBean，那么我们可以定义一个条件类来检查某个类是否在容器中已被注册。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserDAOBeanNotPresentsCondition</span> <span class="keyword">implements</span> <span class="title">Condition</span> </span>&#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)</span> </span>&#123;</span><br><span class="line">		UserDAO userDAO = conditionContext.getBeanFactory().getBean(UserDAO.class);</span><br><span class="line">		<span class="keyword">return</span> (userDAO == <span class="keyword">null</span>);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如果你想根据配置文件中的某项属性来决定是否注册MongoDAO，例如<code>app.dbType</code>是否等于<code>MongoDB</code>，我们可以实现以下的条件类。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MongoDbTypePropertyCondition</span> <span class="keyword">implements</span> <span class="title">Condition</span> </span>&#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)</span> </span>&#123;</span><br><span class="line">		String dbType = conditionContext.getEnvironment().getProperty(<span class="string">&quot;app.dbType&quot;</span>);</span><br><span class="line">		<span class="keyword">return</span> <span class="string">&quot;MONGO&quot;</span>.equalsIgnoreCase(dbType);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们已经尝试并实现了各种类型的条件判断，接下来，我们可以选择一种更为优雅的方式，就像@Profile一样，以注解的方式来完成条件判断。首先，我们需要定义一个注解类。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target(&#123; ElementType.TYPE, ElementType.METHOD &#125;)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Conditional(DatabaseTypeCondition.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> DatabaseType &#123;</span><br><span class="line">	<span class="function">String <span class="title">value</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>具体的条件判断逻辑在DatabaseTypeCondition类中，它会根据系统参数<code>dbType</code>来判断注册哪一个Bean。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DatabaseTypeCondition</span> <span class="keyword">implements</span> <span class="title">Condition</span> </span>&#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)</span> </span>&#123;</span><br><span class="line">		Map&lt;String, Object&gt; attributes = metadata</span><br><span class="line">											.getAnnotationAttributes(DatabaseType.class.getName());</span><br><span class="line">		String type = (String) attributes.get(<span class="string">&quot;value&quot;</span>);</span><br><span class="line">		<span class="comment">// 默认值为MySql</span></span><br><span class="line">		String enabledDBType = System.getProperty(<span class="string">&quot;dbType&quot;</span>, <span class="string">&quot;MySql&quot;</span>);</span><br><span class="line">		<span class="keyword">return</span> (enabledDBType != <span class="keyword">null</span> &amp;&amp; type != <span class="keyword">null</span> &amp;&amp; enabledDBType.equalsIgnoreCase(type));</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>最后，在配置类应用该注解即可。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AppConfig</span> </span>&#123;</span><br><span class="line">	<span class="meta">@Bean</span></span><br><span class="line">	<span class="meta">@DatabaseType(&quot;MySql&quot;)</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> UserDAO <span class="title">jdbcUserDAO</span><span class="params">()</span> </span>&#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="keyword">new</span> JdbcUserDAO();</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="meta">@Bean</span></span><br><span class="line">	<span class="meta">@DatabaseType(&quot;mongoDB&quot;)</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> UserDAO <span class="title">mongoUserDAO</span><span class="params">()</span> </span>&#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="keyword">new</span> MongoUserDAO();</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="AutoConfigure源码分析"><a href="#AutoConfigure源码分析" class="headerlink" title="AutoConfigure源码分析"></a>AutoConfigure源码分析</h3><hr>
<p>通过了解@Conditional注解的机制其实已经能够猜到自动配置是如何实现的了，接下来我们通过源码来看看它是怎么做的。本文中讲解的源码基于Spring Boot 1.5.9版本（最新的正式版本）。</p>
<p>使用过Spring Boot的童鞋应该都很清楚，它会替我们生成一个入口类，其命名规格为<code>ArtifactNameApplication</code>，通过这个入口类，我们可以发现一些信息。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoApplication</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(DemoApplication.class, args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>首先该类被@SpringBootApplication注解修饰，我们可以先从它开始分析，查看源码后可以发现它是一个包含许多注解的组合注解。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target(&#123;ElementType.TYPE&#125;)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Inherited</span></span><br><span class="line"><span class="meta">@SpringBootConfiguration</span></span><br><span class="line"><span class="meta">@EnableAutoConfiguration</span></span><br><span class="line"><span class="meta">@ComponentScan(</span></span><br><span class="line"><span class="meta">    excludeFilters = &#123;@Filter(</span></span><br><span class="line"><span class="meta">    type = FilterType.CUSTOM,</span></span><br><span class="line"><span class="meta">    classes = &#123;TypeExcludeFilter.class&#125;</span></span><br><span class="line"><span class="meta">), @Filter(</span></span><br><span class="line"><span class="meta">    type = FilterType.CUSTOM,</span></span><br><span class="line"><span class="meta">    classes = &#123;AutoConfigurationExcludeFilter.class&#125;</span></span><br><span class="line"><span class="meta">)&#125;</span></span><br><span class="line"><span class="meta">)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> SpringBootApplication &#123;</span><br><span class="line">    <span class="meta">@AliasFor(</span></span><br><span class="line"><span class="meta">        annotation = EnableAutoConfiguration.class,</span></span><br><span class="line"><span class="meta">        attribute = &quot;exclude&quot;</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    Class&lt;?&gt;[] exclude() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@AliasFor(</span></span><br><span class="line"><span class="meta">        annotation = EnableAutoConfiguration.class,</span></span><br><span class="line"><span class="meta">        attribute = &quot;excludeName&quot;</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    String[] excludeName() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@AliasFor(</span></span><br><span class="line"><span class="meta">        annotation = ComponentScan.class,</span></span><br><span class="line"><span class="meta">        attribute = &quot;basePackages&quot;</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    String[] scanBasePackages() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@AliasFor(</span></span><br><span class="line"><span class="meta">        annotation = ComponentScan.class,</span></span><br><span class="line"><span class="meta">        attribute = &quot;basePackageClasses&quot;</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    Class&lt;?&gt;[] scanBasePackageClasses() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>该注解相当于同时声明了@Configuration、@EnableAutoConfiguration与@ComponentScan三个注解（如果我们想定制自定义的自动配置实现，声明这三个注解就足够了），而@EnableAutoConfiguration是我们的关注点，从它的名字可以看出来，它是用来开启自动配置的，源码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target(&#123;ElementType.TYPE&#125;)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Inherited</span></span><br><span class="line"><span class="meta">@AutoConfigurationPackage</span></span><br><span class="line"><span class="meta">@Import(&#123;EnableAutoConfigurationImportSelector.class&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> EnableAutoConfiguration &#123;</span><br><span class="line">    String ENABLED_OVERRIDE_PROPERTY = <span class="string">&quot;spring.boot.enableautoconfiguration&quot;</span>;</span><br><span class="line"></span><br><span class="line">    Class&lt;?&gt;[] exclude() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line"></span><br><span class="line">    String[] excludeName() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们发现@Import（Spring 提供的一个注解，可以导入配置类或者Bean到当前类中）导入了EnableAutoConfigurationImportSelector类，根据名字来看，它应该就是我们要找到的目标了。不过查看它的源码发现它已经被Deprecated了，而官方API中告知我们去查看它的父类AutoConfigurationImportSelector。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** <span class="doctag">@deprecated</span> */</span></span><br><span class="line"><span class="meta">@Deprecated</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EnableAutoConfigurationImportSelector</span> <span class="keyword">extends</span> <span class="title">AutoConfigurationImportSelector</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">EnableAutoConfigurationImportSelector</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">isEnabled</span><span class="params">(AnnotationMetadata metadata)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.getClass().equals(EnableAutoConfigurationImportSelector.class)?((Boolean)<span class="keyword">this</span>.getEnvironment().getProperty(<span class="string">&quot;spring.boot.enableautoconfiguration&quot;</span>, Boolean.class, Boolean.valueOf(<span class="keyword">true</span>))).booleanValue():<span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>由于AutoConfigurationImportSelector的源码太长了，这里我只截出关键的地方，显然方法selectImports是选择自动配置的主入口，它调用了其他的几个方法来加载元数据等信息，最后返回一个包含许多自动配置类信息的字符串数组。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> String[] selectImports(AnnotationMetadata annotationMetadata) &#123;</span><br><span class="line">    <span class="keyword">if</span>(!<span class="keyword">this</span>.isEnabled(annotationMetadata)) &#123;</span><br><span class="line">        <span class="keyword">return</span> NO_IMPORTS;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            AutoConfigurationMetadata ex = AutoConfigurationMetadataLoader.loadMetadata(<span class="keyword">this</span>.beanClassLoader);</span><br><span class="line">            AnnotationAttributes attributes = <span class="keyword">this</span>.getAttributes(annotationMetadata);</span><br><span class="line">            List configurations = <span class="keyword">this</span>.getCandidateConfigurations(annotationMetadata, attributes);</span><br><span class="line">            configurations = <span class="keyword">this</span>.removeDuplicates(configurations);</span><br><span class="line">            configurations = <span class="keyword">this</span>.sort(configurations, ex);</span><br><span class="line">            Set exclusions = <span class="keyword">this</span>.getExclusions(annotationMetadata, attributes);</span><br><span class="line">            <span class="keyword">this</span>.checkExcludedClasses(configurations, exclusions);</span><br><span class="line">            configurations.removeAll(exclusions);</span><br><span class="line">            configurations = <span class="keyword">this</span>.filter(configurations, ex);</span><br><span class="line">            <span class="keyword">this</span>.fireAutoConfigurationImportEvents(configurations, exclusions);</span><br><span class="line">            <span class="keyword">return</span> (String[])configurations.toArray(<span class="keyword">new</span> String[configurations.size()]);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException var6) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(var6);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>重点在于方法getCandidateConfigurations()返回了自动配置类的信息列表，而它通过调用SpringFactoriesLoader.loadFactoryNames()来扫描加载含有META-INF/spring.factories文件的jar包，该文件记录了具有哪些自动配置类。（建议还是用IDE去看源码吧，这些源码单行实在太长了，估计文章中的观看效果很差）</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> List&lt;String&gt; <span class="title">getCandidateConfigurations</span><span class="params">(AnnotationMetadata metadata, AnnotationAttributes attributes)</span> </span>&#123;</span><br><span class="line">    List configurations = SpringFactoriesLoader</span><br><span class="line">    									.loadFactoryNames(<span class="keyword">this</span>.getSpringFactoriesLoaderFactoryClass(), <span class="keyword">this</span>.getBeanClassLoader());</span><br><span class="line">    Assert.notEmpty(configurations, <span class="string">&quot;No auto configuration classes </span></span><br><span class="line"><span class="string">    found in META-INF spring.factories. </span></span><br><span class="line"><span class="string">    If you are using a custom packaging, make sure that file is correct.&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> configurations;</span><br><span class="line">&#125;</span><br><span class="line">	</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> List&lt;String&gt; <span class="title">loadFactoryNames</span><span class="params">(Class&lt;?&gt; factoryClass, ClassLoader classLoader)</span> </span>&#123;</span><br><span class="line">    String factoryClassName = factoryClass.getName();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        Enumeration ex = classLoader != <span class="keyword">null</span>?classLoader.getResources(<span class="string">&quot;META-INF/spring.factories&quot;</span>):ClassLoader.getSystemResources(<span class="string">&quot;META-INF/spring.factories&quot;</span>);</span><br><span class="line">        ArrayList result = <span class="keyword">new</span> ArrayList();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span>(ex.hasMoreElements()) &#123;</span><br><span class="line">            URL url = (URL)ex.nextElement();</span><br><span class="line">            Properties properties = PropertiesLoaderUtils.loadProperties(<span class="keyword">new</span> UrlResource(url));</span><br><span class="line">            String factoryClassNames = properties.getProperty(factoryClassName);</span><br><span class="line">            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException var8) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">&quot;Unable to load [&quot;</span> + factoryClass.getName() + <span class="string">&quot;] factories from location [&quot;</span> + <span class="string">&quot;META-INF/spring.factories&quot;</span> + <span class="string">&quot;]&quot;</span>, var8);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;	</span><br></pre></td></tr></table></figure>
<p><img src="http://wx2.sinaimg.cn/large/63503acbly1fn9iobng7zj21270o9gpg.jpg" alt="spring.factories"></p>
<h3 id="自动配置类中的条件注解"><a href="#自动配置类中的条件注解" class="headerlink" title="自动配置类中的条件注解"></a>自动配置类中的条件注解</h3><hr>
<p>接下来，我们在spring.factories文件中随便找一个自动配置类，来看看是怎样实现的。我查看了MongoDataAutoConfiguration的源码，发现它声明了@ConditionalOnClass注解，通过看该注解的源码后可以发现，这是一个组合了@Conditional的组合注解，它的条件类是OnClassCondition。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ConditionalOnClass(&#123;Mongo.class, MongoTemplate.class&#125;)</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(&#123;MongoProperties.class&#125;)</span></span><br><span class="line"><span class="meta">@AutoConfigureAfter(&#123;MongoAutoConfiguration.class&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MongoDataAutoConfiguration</span> </span>&#123;</span><br><span class="line">	....</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Target(&#123;ElementType.TYPE, ElementType.METHOD&#125;)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Conditional(&#123;OnClassCondition.class&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> ConditionalOnClass &#123;</span><br><span class="line">    Class&lt;?&gt;[] value() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line"></span><br><span class="line">    String[] name() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然后，我们开始看OnClassCondition的源码，发现它并没有直接实现Condition接口，只好往上找，发现它的父类SpringBootCondition实现了Condition接口。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">OnClassCondition</span> <span class="keyword">extends</span> <span class="title">SpringBootCondition</span> <span class="keyword">implements</span> <span class="title">AutoConfigurationImportFilter</span>, <span class="title">BeanFactoryAware</span>, <span class="title">BeanClassLoaderAware</span> </span>&#123;</span><br><span class="line">	.....</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringBootCondition</span> <span class="keyword">implements</span> <span class="title">Condition</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Log logger = LogFactory.getLog(<span class="keyword">this</span>.getClass());</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">SpringBootCondition</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(ConditionContext context, AnnotatedTypeMetadata metadata)</span> </span>&#123;</span><br><span class="line">        String classOrMethodName = getClassOrMethodName(metadata);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            ConditionOutcome ex = <span class="keyword">this</span>.getMatchOutcome(context, metadata);</span><br><span class="line">            <span class="keyword">this</span>.logOutcome(classOrMethodName, ex);</span><br><span class="line">            <span class="keyword">this</span>.recordEvaluation(context, classOrMethodName, ex);</span><br><span class="line">            <span class="keyword">return</span> ex.isMatch();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (NoClassDefFoundError var5) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">&quot;Could not evaluate condition on &quot;</span> + classOrMethodName + <span class="string">&quot; due to &quot;</span> + var5.getMessage() + <span class="string">&quot; not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)&quot;</span>, var5);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (RuntimeException var6) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">&quot;Error processing condition on &quot;</span> + <span class="keyword">this</span>.getName(metadata), var6);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> ConditionOutcome <span class="title">getMatchOutcome</span><span class="params">(ConditionContext var1, AnnotatedTypeMetadata var2)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>SpringBootCondition实现的matches方法依赖于一个抽象方法this.getMatchOutcome(context, metadata)，我们在它的子类OnClassCondition中可以找到这个方法的具体实现。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> ConditionOutcome <span class="title">getMatchOutcome</span><span class="params">(ConditionContext context, AnnotatedTypeMetadata metadata)</span> </span>&#123;</span><br><span class="line">    ClassLoader classLoader = context.getClassLoader();</span><br><span class="line">    ConditionMessage matchMessage = ConditionMessage.empty();</span><br><span class="line">    <span class="comment">// 找出所有ConditionalOnClass注解的属性</span></span><br><span class="line">    List onClasses = <span class="keyword">this</span>.getCandidates(metadata, ConditionalOnClass.class);</span><br><span class="line">    List onMissingClasses;</span><br><span class="line">    <span class="keyword">if</span>(onClasses != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 找出不在类路径中的类</span></span><br><span class="line">        onMissingClasses = <span class="keyword">this</span>.getMatches(onClasses, OnClassCondition.MatchType.MISSING, classLoader);</span><br><span class="line">        <span class="comment">// 如果存在不在类路径中的类，匹配失败</span></span><br><span class="line">        <span class="keyword">if</span>(!onMissingClasses.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">return</span> ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, <span class="keyword">new</span> Object[<span class="number">0</span>]).didNotFind(<span class="string">&quot;required class&quot;</span>, <span class="string">&quot;required classes&quot;</span>).items(Style.QUOTE, onMissingClasses));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        matchMessage = matchMessage.andCondition(ConditionalOnClass.class, <span class="keyword">new</span> Object[<span class="number">0</span>]).found(<span class="string">&quot;required class&quot;</span>, <span class="string">&quot;required classes&quot;</span>).items(Style.QUOTE, <span class="keyword">this</span>.getMatches(onClasses, OnClassCondition.MatchType.PRESENT, classLoader));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 接着找出所有ConditionalOnMissingClass注解的属性</span></span><br><span class="line">    <span class="comment">// 它与ConditionalOnClass注解的含义正好相反，所以以下逻辑也与上面相反</span></span><br><span class="line">    onMissingClasses = <span class="keyword">this</span>.getCandidates(metadata, ConditionalOnMissingClass.class);</span><br><span class="line">    <span class="keyword">if</span>(onMissingClasses != <span class="keyword">null</span>) &#123;</span><br><span class="line">        List present = <span class="keyword">this</span>.getMatches(onMissingClasses, OnClassCondition.MatchType.PRESENT, classLoader);</span><br><span class="line">        <span class="keyword">if</span>(!present.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">return</span> ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, <span class="keyword">new</span> Object[<span class="number">0</span>]).found(<span class="string">&quot;unwanted class&quot;</span>, <span class="string">&quot;unwanted classes&quot;</span>).items(Style.QUOTE, present));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, <span class="keyword">new</span> Object[<span class="number">0</span>]).didNotFind(<span class="string">&quot;unwanted class&quot;</span>, <span class="string">&quot;unwanted classes&quot;</span>).items(Style.QUOTE, <span class="keyword">this</span>.getMatches(onMissingClasses, OnClassCondition.MatchType.MISSING, classLoader));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> ConditionOutcome.match(matchMessage);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获得所有annotationType注解的属性</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> List&lt;String&gt; <span class="title">getCandidates</span><span class="params">(AnnotatedTypeMetadata metadata, Class&lt;?&gt; annotationType)</span> </span>&#123;</span><br><span class="line">    MultiValueMap attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), <span class="keyword">true</span>);</span><br><span class="line">    ArrayList candidates = <span class="keyword">new</span> ArrayList();</span><br><span class="line">    <span class="keyword">if</span>(attributes == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Collections.emptyList();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.addAll(candidates, (List)attributes.get(<span class="string">&quot;value&quot;</span>));</span><br><span class="line">        <span class="keyword">this</span>.addAll(candidates, (List)attributes.get(<span class="string">&quot;name&quot;</span>));</span><br><span class="line">        <span class="keyword">return</span> candidates;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">addAll</span><span class="params">(List&lt;String&gt; list, List&lt;Object&gt; itemsToAdd)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(itemsToAdd != <span class="keyword">null</span>) &#123;</span><br><span class="line">        Iterator var3 = itemsToAdd.iterator();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span>(var3.hasNext()) &#123;</span><br><span class="line">            Object item = var3.next();</span><br><span class="line">            Collections.addAll(list, (String[])((String[])item));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;    </span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据matchType.matches方法来进行匹配</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> List&lt;String&gt; <span class="title">getMatches</span><span class="params">(Collection&lt;String&gt; candidates, OnClassCondition.MatchType matchType, ClassLoader classLoader)</span> </span>&#123;</span><br><span class="line">    ArrayList matches = <span class="keyword">new</span> ArrayList(candidates.size());</span><br><span class="line">    Iterator var5 = candidates.iterator();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span>(var5.hasNext()) &#123;</span><br><span class="line">        String candidate = (String)var5.next();</span><br><span class="line">        <span class="keyword">if</span>(matchType.matches(candidate, classLoader)) &#123;</span><br><span class="line">            matches.add(candidate);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> matches;</span><br><span class="line">&#125;    </span><br></pre></td></tr></table></figure>
<p>关于match的具体实现在MatchType中，它是一个枚举类，提供了PRESENT和MISSING两种实现，前者返回类路径中是否存在该类，后者相反。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">enum</span> <span class="title">MatchType</span> </span>&#123;</span><br><span class="line">    PRESENT &#123;</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(String className, ClassLoader classLoader)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> OnClassCondition.MatchType.isPresent(className, classLoader);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    MISSING &#123;</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(String className, ClassLoader classLoader)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> !OnClassCondition.MatchType.isPresent(className, classLoader);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">MatchType</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 跟我们之前看过的案例一样，都利用了类加载功能来进行判断</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isPresent</span><span class="params">(String className, ClassLoader classLoader)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(classLoader == <span class="keyword">null</span>) &#123;</span><br><span class="line">            classLoader = ClassUtils.getDefaultClassLoader();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            forName(className, classLoader);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable var3) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Class&lt;?&gt; forName(String className, ClassLoader classLoader) <span class="keyword">throws</span> ClassNotFoundException &#123;</span><br><span class="line">        <span class="keyword">return</span> classLoader != <span class="keyword">null</span>?classLoader.loadClass(className):Class.forName(className);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(String var1, ClassLoader var2)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>现在终于真相大白，@ConditionalOnClass的含义是指定的类必须存在于类路径下，MongoDataAutoConfiguration类中声明了类路径下必须含有Mongo.class, MongoTemplate.class这两个类，否则该自动配置类不会被加载。</p>
<p>在Spring Boot中到处都有类似的注解，像@ConditionalOnBean（容器中是否有指定的Bean），@ConditionalOnWebApplication（当前工程是否为一个Web工程）等等，它们都只是@Conditional注解的扩展。当你揭开神秘的面纱，去探索本质时，发现其实Spring Boot自动配置的原理就是如此简单，在了解这些知识后，你完全可以自己去实现自定义的自动配置类，然后编写出自定义的starter。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh">
    <link itemprop="mainEntityOfPage" href="https://suyuhuan.gitee.io/yuwanzi.io/2017/12/31/2017-12-31-skip_list/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/yuwanzi.io/images/avatar.gif">
      <meta itemprop="name" content="玉丸子">
      <meta itemprop="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="玉丸子 | Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/yuwanzi.io/2017/12/31/2017-12-31-skip_list/" class="post-title-link" itemprop="url">SkipList的那点事儿</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Veröffentlicht am</span>

      <time title="Erstellt: 2017-12-31 18:00:00" itemprop="dateCreated datePublished" datetime="2017-12-31T18:00:00+08:00">2017-12-31</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">Bearbeitet am</span>
        <time title="Geändert am: 2020-11-07 08:58:17" itemprop="dateModified" datetime="2020-11-07T08:58:17+08:00">2020-11-07</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">in</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/Algorithms/" itemprop="url" rel="index"><span itemprop="name">Algorithms</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/Algorithms/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/" itemprop="url" rel="index"><span itemprop="name">数据结构</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h3 id="Skip-List的工作原理"><a href="#Skip-List的工作原理" class="headerlink" title="Skip List的工作原理"></a>Skip List的工作原理</h3><hr>
<p>Skip List（跳跃表）是一种支持快速查找的数据结构，插入、查找和删除操作都仅仅只需要<code>O(log n)</code>对数级别的时间复杂度，它的效率甚至可以与红黑树等二叉平衡树相提并论，而且实现的难度要比红黑树简单多了。</p>
<p>Skip List主要思想是将链表与二分查找相结合，它维护了一个多层级的链表结构（用空间换取时间），可以把Skip List看作一个含有多个行的链表集合，每一行就是一条链表，这样的一行链表被称为一层，每一层都是下一层的”快速通道”，即如果x层和y层都含有元素a，那么x层的a会与y层的a相互连接（垂直）。最底层的链表是含有所有节点的普通序列，而越接近顶层的链表，含有的节点则越少。</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/8/86/Skip_list.svg"></p>
<p>对一个目标元素的搜索会从顶层链表的头部元素开始，然后遍历该链表，直到找到元素大于或等于目标元素的节点，如果当前元素正好等于目标，那么就直接返回它。如果当前元素小于目标元素，那么就垂直下降到下一层继续搜索，如果当前元素大于目标或到达链表尾部，则移动到前一个节点的位置，然后垂直下降到下一层。正因为Skip List的搜索过程会不断地从一层跳跃到下一层的，所以被称为跳跃表。</p>
<p>Skip List还有一个明显的特征，即它是一个不准确的概率性结构，这是因为Skip List在决定是否将节点冗余复制到上一层的时候（而在到达或超过顶层时，需要构建新的顶层）依赖于一个概率函数，举个栗子，我们使用一个最简单的概率函数：丢硬币，即概率<code>P</code>为<code>0.5</code>，那么依赖于该概率函数实现的Skip List会不断地”丢硬币”，如果硬币为正面就将节点复制到上一层，直到硬币为反。</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/2/2c/Skip_list_add_element-en.gif" alt="插入元素的过程"></p>
<p>理解Skip List的原理并不困难，下面我们将使用Java来动手实现一个支持基本需求（查找，插入和删除）的Skip List。</p>
<blockquote>
<p>本文作者为<a target="_blank" rel="noopener" href="https://github.com/SylvanasSun">SylvanasSun(sylvanas.sun@gmail.com)</a>，首发于<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/">SylvanasSun’s Blog</a>。<br>原文链接：<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/2017/12/31/2017-12-31-skip_list/">https://sylvanassun.github.io/2017/12/31/2017-12-31-skip_list/</a><br>（转载请务必保留本段声明，并且保留超链接。）</p>
</blockquote>
<h3 id="节点与基本实现"><a href="#节点与基本实现" class="headerlink" title="节点与基本实现"></a>节点与基本实现</h3><hr>
<p>对于一个普通的链表节点一般只含有一个指向后续节点的指针（双向链表的节点含有两个指针，一个指向前节点，一个指向后节点），由于Skip List是一个多层级的链表结构，我们的设计要让节点拥有四个指针，分别对应该节点的前后左右，为了方便地将头链表永远置于顶层，还需要设置一个int属性表示该链表所处的层级。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">    <span class="keyword">protected</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span>&lt;<span class="title">K</span> <span class="keyword">extends</span> <span class="title">Comparable</span>&lt;<span class="title">K</span>&gt;, <span class="title">V</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> K key;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> V value;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">int</span> level; <span class="comment">// 该节点所处的层级</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> Node&lt;K, V&gt; up, down, next, previous;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">Node</span><span class="params">(K key, V value, <span class="keyword">int</span> level)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.key = key;</span><br><span class="line">            <span class="keyword">this</span>.value = value;</span><br><span class="line">            <span class="keyword">this</span>.level = level;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            StringBuilder sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line">            sb.append(<span class="string">&quot;Node[&quot;</span>)</span><br><span class="line">                    .append(<span class="string">&quot;key:&quot;</span>);</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">this</span>.key == <span class="keyword">null</span>)</span><br><span class="line">                sb.append(<span class="string">&quot;None&quot;</span>);</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                sb.append(<span class="keyword">this</span>.key.toString());</span><br><span class="line"></span><br><span class="line">            sb.append(<span class="string">&quot; value:&quot;</span>);</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">this</span>.value == <span class="keyword">null</span>)</span><br><span class="line">                sb.append(<span class="string">&quot;None&quot;</span>);</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                sb.append(<span class="keyword">this</span>.value.toString());</span><br><span class="line">            sb.append(<span class="string">&quot;]&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> sb.toString();</span><br><span class="line">        &#125;</span><br><span class="line">		</span><br><span class="line">		<span class="comment">// 余下都是get,set方法, 这里省略</span></span><br><span class="line">		.....</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>接下来是SkipList的基本实现，为了能够让Key进行比较，我们规定Key的类型必须实现了Comparable接口，同时为了支持ForEach循环，该类还实现了Iterable接口。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SkipList</span>&lt;<span class="title">K</span> <span class="keyword">extends</span> <span class="title">Comparable</span>&lt;<span class="title">K</span>&gt;, <span class="title">V</span>&gt; <span class="keyword">implements</span> <span class="title">Iterable</span>&lt;<span class="title">K</span>&gt; </span>&#123;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 一个随机数生成器</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">final</span> Random randomGenerator = <span class="keyword">new</span> Random();</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 默认的概率</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">double</span> DEFAULT_PROBABILITY = <span class="number">0.5</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 头节点</span></span><br><span class="line">    <span class="keyword">private</span> Node&lt;K, V&gt; head;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">double</span> probability;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// SkipList中的元素数量（不计算多个层级中的冗余元素）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> size;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">SkipList</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(DEFAULT_PROBABILITY);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">SkipList</span><span class="params">(<span class="keyword">double</span> probability)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.head = <span class="keyword">new</span> Node&lt;K, V&gt;(<span class="keyword">null</span>, <span class="keyword">null</span>, <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">this</span>.probability = probability;</span><br><span class="line">        <span class="keyword">this</span>.size = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">	.....</span><br><span class="line">&#125;	</span><br></pre></td></tr></table></figure>
<p>我们还需要定义几个辅助方法，如下所示（都很简单）：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 对key进行检查</span></span><br><span class="line"><span class="comment">// 因为每条链表的头节点就是一个key为null的节点，所以不允许其他节点的key也为null</span></span><br><span class="line">   <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">checkKeyValidity</span><span class="params">(K key)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">if</span> (key == <span class="keyword">null</span>)</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">&quot;Key must be not null!&quot;</span>);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// a是否小于等于b</span></span><br><span class="line">   <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">lessThanOrEqual</span><span class="params">(K a, K b)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">return</span> a.compareTo(b) &lt;= <span class="number">0</span>;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 概率函数</span></span><br><span class="line">   <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">isBuildLevel</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       <span class="keyword">return</span> randomGenerator.nextDouble() &lt; probability;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将y水平插入到x的后面</span></span><br><span class="line">   <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">horizontalInsert</span><span class="params">(Node&lt;K, V&gt; x, Node&lt;K, V&gt; y)</span> </span>&#123;</span><br><span class="line">       y.setPrevious(x);</span><br><span class="line">       y.setNext(x.getNext());</span><br><span class="line">       <span class="keyword">if</span> (x.getNext() != <span class="keyword">null</span>)</span><br><span class="line">           x.getNext().setPrevious(y);</span><br><span class="line">       x.setNext(y);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// x与y进行垂直连接</span></span><br><span class="line">   <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">verticalLink</span><span class="params">(Node&lt;K, V&gt; x, Node&lt;K, V&gt; y)</span> </span>&#123;</span><br><span class="line">       x.setDown(y);</span><br><span class="line">       y.setUp(x);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>
<h3 id="查找"><a href="#查找" class="headerlink" title="查找"></a>查找</h3><hr>
<p>查找一个节点的过程如下：</p>
<ul>
<li><p>从顶层链表的头部开始进行遍历，比较每一个节点的元素与目标元素的大小。</p>
</li>
<li><p>如果当前元素小于目标元素，则继续遍历。</p>
</li>
<li><p>如果当前元素等于目标元素，返回该节点。</p>
</li>
<li><p>如果当前元素大于目标元素，移动到前一个节点（必须小于等于目标元素），然后跳跃到下一层继续遍历。</p>
</li>
<li><p>如果遍历至链表尾部，跳跃到下一层继续遍历。</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">  <span class="function"><span class="keyword">protected</span> Node&lt;K, V&gt; <span class="title">findNode</span><span class="params">(K key)</span> </span>&#123;</span><br><span class="line">      Node&lt;K, V&gt; node = head;</span><br><span class="line">      Node&lt;K, V&gt; next = <span class="keyword">null</span>;</span><br><span class="line">      Node&lt;K, V&gt; down = <span class="keyword">null</span>;</span><br><span class="line">      K nodeKey = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">          <span class="comment">// 不断遍历直到遇见大于目标元素的节点</span></span><br><span class="line">          next = node.getNext();</span><br><span class="line">          <span class="keyword">while</span> (next != <span class="keyword">null</span> &amp;&amp; lessThanOrEqual(next.getKey(), key)) &#123;</span><br><span class="line">              node = next;</span><br><span class="line">              next = node.getNext();</span><br><span class="line">          &#125;</span><br><span class="line">	<span class="comment">// 当前元素等于目标元素，中断循环</span></span><br><span class="line">          nodeKey = node.getKey();</span><br><span class="line">          <span class="keyword">if</span> (nodeKey != <span class="keyword">null</span> &amp;&amp; nodeKey.compareTo(key) == <span class="number">0</span>)</span><br><span class="line">              <span class="keyword">break</span>;</span><br><span class="line">          <span class="comment">// 否则，跳跃到下一层级</span></span><br><span class="line">          down = node.getDown();</span><br><span class="line">          <span class="keyword">if</span> (down != <span class="keyword">null</span>) &#123;</span><br><span class="line">              node = down;</span><br><span class="line">          &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">              <span class="keyword">break</span>;</span><br><span class="line">          &#125;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> node;</span><br><span class="line">  &#125;</span><br><span class="line">	</span><br><span class="line">  <span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(K key)</span> </span>&#123;</span><br><span class="line">      checkKeyValidity(key);</span><br><span class="line">      Node&lt;K, V&gt; node = findNode(key);</span><br><span class="line"><span class="comment">// 如果找到的节点并不等于目标元素，则目标元素不存在于SkipList中</span></span><br><span class="line">      <span class="keyword">if</span> (node.getKey().compareTo(key) == <span class="number">0</span>)</span><br><span class="line">          <span class="keyword">return</span> node.getValue();</span><br><span class="line">      <span class="keyword">else</span></span><br><span class="line">          <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">  &#125;	</span><br></pre></td></tr></table></figure>
<h3 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h3><hr>
<p>插入操作的过程要稍微复杂些，主要在于复制节点到上一层与构建新层的操作上。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">add</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">      checkKeyValidity(key);</span><br><span class="line"><span class="comment">// 直接找到key，然后修改对应的value即可</span></span><br><span class="line">      Node&lt;K, V&gt; node = findNode(key);</span><br><span class="line">      <span class="keyword">if</span> (node.getKey() != <span class="keyword">null</span> &amp;&amp; node.getKey().compareTo(key) == <span class="number">0</span>) &#123;</span><br><span class="line">          node.setValue(value);</span><br><span class="line">          <span class="keyword">return</span>;</span><br><span class="line">      &#125;</span><br><span class="line">	</span><br><span class="line"><span class="comment">// 将newNode水平插入到node之后</span></span><br><span class="line">      Node&lt;K, V&gt; newNode = <span class="keyword">new</span> Node&lt;K, V&gt;(key, value, node.getLevel());</span><br><span class="line">      horizontalInsert(node, newNode);</span><br><span class="line">      </span><br><span class="line">      <span class="keyword">int</span> currentLevel = node.getLevel();</span><br><span class="line">      <span class="keyword">int</span> headLevel = head.getLevel();</span><br><span class="line">      <span class="keyword">while</span> (isBuildLevel()) &#123;</span><br><span class="line">          <span class="comment">// 如果当前层级已经到达或超越顶层</span></span><br><span class="line">	<span class="comment">// 那么需要构建一个新的顶层</span></span><br><span class="line">          <span class="keyword">if</span> (currentLevel &gt;= headLevel) &#123;</span><br><span class="line">              Node&lt;K, V&gt; newHead = <span class="keyword">new</span> Node&lt;K, V&gt;(<span class="keyword">null</span>, <span class="keyword">null</span>, headLevel + <span class="number">1</span>);</span><br><span class="line">              verticalLink(newHead, head);</span><br><span class="line">              head = newHead;</span><br><span class="line">              headLevel = head.getLevel();</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="comment">// 找到node对应的上一层节点</span></span><br><span class="line">          <span class="keyword">while</span> (node.getUp() == <span class="keyword">null</span>) &#123;</span><br><span class="line">              node = node.getPrevious();</span><br><span class="line">          &#125;</span><br><span class="line">          node = node.getUp();</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 将newNode复制到上一层</span></span><br><span class="line">          Node&lt;K, V&gt; tmp = <span class="keyword">new</span> Node&lt;K, V&gt;(key, value, node.getLevel());</span><br><span class="line">          horizontalInsert(node, tmp);</span><br><span class="line">          verticalLink(tmp, newNode);</span><br><span class="line">          newNode = tmp;</span><br><span class="line">          currentLevel++;</span><br><span class="line">      &#125;</span><br><span class="line">      size++;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<h3 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h3><hr>
<p>对于删除一个节点，需要先找到节点所在的位置（位于最底层链表中的位置），之后再自底向上地删除该节点在每一行中的冗余复制。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">(K key)</span> </span>&#123;</span><br><span class="line">    checkKeyValidity(key);</span><br><span class="line">    Node&lt;K, V&gt; node = findNode(key);</span><br><span class="line">    <span class="keyword">if</span> (node == <span class="keyword">null</span> || node.getKey().compareTo(key) != <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NoSuchElementException(<span class="string">&quot;The key is not exist!&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 移动到最底层</span></span><br><span class="line">    <span class="keyword">while</span> (node.getDown() != <span class="keyword">null</span>)</span><br><span class="line">        node = node.getDown();</span><br><span class="line">    <span class="comment">// 自底向上地进行删除</span></span><br><span class="line">    Node&lt;K, V&gt; prev = <span class="keyword">null</span>;</span><br><span class="line">    Node&lt;K, V&gt; next = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">for</span> (; node != <span class="keyword">null</span>; node = node.getUp()) &#123;</span><br><span class="line">        prev = node.getPrevious();</span><br><span class="line">        next = node.getNext();</span><br><span class="line">        <span class="keyword">if</span> (prev != <span class="keyword">null</span>)</span><br><span class="line">            prev.setNext(next);</span><br><span class="line">        <span class="keyword">if</span> (next != <span class="keyword">null</span>)</span><br><span class="line">            next.setPrevious(prev);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 对顶层链表进行调整，去除无效的顶层链表</span></span><br><span class="line">    <span class="keyword">while</span> (head.getNext() == <span class="keyword">null</span> &amp;&amp; head.getDown() != <span class="keyword">null</span>) &#123;</span><br><span class="line">        head = head.getDown();</span><br><span class="line">        head.setUp(<span class="keyword">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    size--;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="迭代器"><a href="#迭代器" class="headerlink" title="迭代器"></a>迭代器</h3><hr>
<p>由于我们的SkipList实现了Iterable接口，所以还需要实现一个迭代器。对于迭代一个Skip List，只需要找到最底层的链表并且移动到它的首节点，然后进行遍历即可。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      StringBuilder sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line">      Node&lt;K, V&gt; node = head;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 移动到最底层</span></span><br><span class="line">      <span class="keyword">while</span> (node.getDown() != <span class="keyword">null</span>)</span><br><span class="line">          node = node.getDown();</span><br><span class="line"></span><br><span class="line">      <span class="keyword">while</span> (node.getPrevious() != <span class="keyword">null</span>)</span><br><span class="line">          node = node.getPrevious();</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 第一个节点是头部节点，没有任何意义，所以需要移动到后一个节点</span></span><br><span class="line">      <span class="keyword">if</span> (node.getNext() != <span class="keyword">null</span>)</span><br><span class="line">          node = node.getNext();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历</span></span><br><span class="line">      <span class="keyword">while</span> (node != <span class="keyword">null</span>) &#123;</span><br><span class="line">          sb.append(node.toString()).append(<span class="string">&quot;\n&quot;</span>);</span><br><span class="line">          node = node.getNext();</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> sb.toString();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> Iterator&lt;K&gt; <span class="title">iterator</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> SkipListIterator&lt;K, V&gt;(head);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">protected</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">SkipListIterator</span>&lt;<span class="title">K</span> <span class="keyword">extends</span> <span class="title">Comparable</span>&lt;<span class="title">K</span>&gt;, <span class="title">V</span>&gt; <span class="keyword">implements</span> <span class="title">Iterator</span>&lt;<span class="title">K</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">private</span> Node&lt;K, V&gt; node;</span><br><span class="line"></span><br><span class="line">      <span class="function"><span class="keyword">public</span> <span class="title">SkipListIterator</span><span class="params">(Node&lt;K, V&gt; node)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">while</span> (node.getDown() != <span class="keyword">null</span>)</span><br><span class="line">              node = node.getDown();</span><br><span class="line"></span><br><span class="line">          <span class="keyword">while</span> (node.getPrevious() != <span class="keyword">null</span>)</span><br><span class="line">              node = node.getPrevious();</span><br><span class="line"></span><br><span class="line">          <span class="keyword">if</span> (node.getNext() != <span class="keyword">null</span>)</span><br><span class="line">              node = node.getNext();</span><br><span class="line"></span><br><span class="line">          <span class="keyword">this</span>.node = node;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">hasNext</span><span class="params">()</span> </span>&#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="keyword">this</span>.node != <span class="keyword">null</span>;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="function"><span class="keyword">public</span> K <span class="title">next</span><span class="params">()</span> </span>&#123;</span><br><span class="line">          K result = node.getKey();</span><br><span class="line">          node = node.getNext();</span><br><span class="line">          <span class="keyword">return</span> result;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">()</span> </span>&#123;</span><br><span class="line">          <span class="keyword">throw</span> <span class="keyword">new</span> UnsupportedOperationException();</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>
<p><a target="_blank" rel="noopener" href="https://github.com/SylvanasSun/algorithms-exercise/blob/master/src/main/java/com/sylvanas/algorithms_exercise/skip_list/SkipList.java">本文中实现的SkipList完整代码地址</a></p>
<h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h3><hr>
<ul>
<li><a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Skip_list">Skip list - Wikipedia</a></li>
</ul>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh">
    <link itemprop="mainEntityOfPage" href="https://suyuhuan.gitee.io/yuwanzi.io/2017/12/20/2017-12-20-naive_bayes/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/yuwanzi.io/images/avatar.gif">
      <meta itemprop="name" content="玉丸子">
      <meta itemprop="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="玉丸子 | Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/yuwanzi.io/2017/12/20/2017-12-20-naive_bayes/" class="post-title-link" itemprop="url">朴素贝叶斯的那点事儿</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Veröffentlicht am</span>

      <time title="Erstellt: 2017-12-20 18:00:00" itemprop="dateCreated datePublished" datetime="2017-12-20T18:00:00+08:00">2017-12-20</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">Bearbeitet am</span>
        <time title="Geändert am: 2020-11-07 08:58:17" itemprop="dateModified" datetime="2020-11-07T08:58:17+08:00">2020-11-07</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">in</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/" itemprop="url" rel="index"><span itemprop="name">机器学习</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E7%9B%91%E7%9D%A3%E5%BC%8F%E5%AD%A6%E4%B9%A0/" itemprop="url" rel="index"><span itemprop="name">监督式学习</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <p>在机器学习领域中，朴素贝叶斯是一种基于贝叶斯定理的简单概率分类器（分类又被称为监督式学习，所谓监督式学习即从已知样本数据中的特征信息去推测可能出现的输出以完成分类，反之聚类问题被称为非监督式学习），朴素贝叶斯在处理文本数据时可以得到较好的分类结果，所以它被广泛应用于文本分类/垃圾邮件过滤/自然语言处理等场景。</p>
<p>朴素贝叶斯假设了样本的每个特征之间是互相独立、互不影响的，比方说，如果有一个水果是红色的，形状为圆形，并且直径大约为70毫米，那么它就有可能被认为是苹果（具有最高概率的类将会被认为是最有可能的类，这被称为最大后验概率 Maximum A Posteriori），即使上述的这些特征可能会有依赖关系或有其他特征存在，朴素贝叶斯都会认为这些特征都独立地贡献了这个水果是一个苹果的概率，这种假设关系太过于理想，所以这也是朴素贝叶斯的”Naive”之处。</p>
<p>朴素贝叶斯的原名为Naive Bayes Classifier，朴素本身并不是一个正确的翻译，之所以这样翻译是因为朴素贝叶斯虽然Naive，但不代表它的效率会差，相反它的优点正在于实现简单与只需要少量的训练数据，还有另一个原因是它与贝叶斯网络等算法相比，确实是“朴素”了些。</p>
<p>在继续探讨朴素贝叶斯之前，我们先需要理解贝叶斯定理与它的前置理论条件概率与全概率公式。</p>
<blockquote>
<p>本文作者为<a target="_blank" rel="noopener" href="https://github.com/SylvanasSun">SylvanasSun(sylvanas.sun@gmail.com)</a>，首发于<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/">SylvanasSun’s Blog</a>。<br>原文链接：<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/2017/12/20/2017-12-20-naive_bayes/">https://sylvanassun.github.io/2017/12/20/2017-12-20-naive_bayes/</a><br>（转载请务必保留本段声明，并且保留超链接。）</p>
</blockquote>
<h3 id="条件概率"><a href="#条件概率" class="headerlink" title="条件概率"></a>条件概率</h3><hr>
<p>条件概率（Conditional Probability）是指在事件B发生的情况下，事件A发生的概率，用$P(A|B)$表示，读作在B条件下的A的概率。</p>
<p><img src="http://wx2.sinaimg.cn/large/63503acbly1fmnd8qww3lj20d308fdfw.jpg"></p>
<p>在上方的文氏图中，描述了两个事件A和B，与它们的交集<code>A ∩ B</code>，代入条件概率公式，可推出事件A发生的概率为$P(A|B) = \frac{P({A}\bigcap{B})}{P(B)}$。</p>
<p>对该公式稍作变换可推得${P({A}\bigcap{B})} = {P(A|B)}{P(B)}$与${P({A}\bigcap{B})} = {P(B|A)}{P(A)}$（<code>P(B|A)</code>为在A条件下的B的概率）。</p>
<p>然后根据这个关系可推得${P(A|B)}{P(B)} = {P(B|A)}{P(A)}$。</p>
<p>让我们举个栗子，假设有两个人在扔两个个六面的骰子<code>D1</code>与<code>D2</code>，我们来预测<code>D1</code>与<code>D2</code>的向上面的结果的概率。</p>
<p><img src="http://wx1.sinaimg.cn/large/63503acbly1fmne20wdcpj20a8078q2w.jpg"></p>
<p>在<code>Table1</code>中描述了一个含有36个结果的样本空间，标红处为<code>D1</code>的向上面为2的6个结果，概率为$P(D1=2) = \frac{6}{36} = \frac{1}{6}$。</p>
<p><img src="http://wx3.sinaimg.cn/large/63503acbly1fmne218l47j209l0723yg.jpg"></p>
<p><code>Table2</code>描述了<code>D1 + D2 &lt;= 5</code>的概率，一共10个结果，用条件概率公式表示为${P(D1+D2\leq5)} = \frac{10}{36}$。</p>
<p><img src="http://wx2.sinaimg.cn/large/63503acbly1fmne21knj0j20a3071q2w.jpg"></p>
<p><code>Table3</code>描述了满足<code>Table2</code>的条件同时也满足<code>D1 = 2</code>的结果，它选中了<code>Table2</code>中的3个结果，用条件概率公式表示为${P(D1=2 | D1+D2\leq5)} = \frac{3}{10} = 0.3$。</p>
<h3 id="全概率公式"><a href="#全概率公式" class="headerlink" title="全概率公式"></a>全概率公式</h3><hr>
<p>全概率公式是将边缘概率与条件概率关联起来的基本规则，它表示了一个结果的总概率，可以通过几个不同的事件来实现。</p>
<p>全概率公式将对一复杂事件的概率求解问题转化为了在不同情况下发生的简单事件的概率的求和问题，公式为$P(B) = {\sum_{i=1}^n}P(A_i)P(B|A_i)$。</p>
<p>假定一个样本空间S，它是两个事件A与C之和，同时事件B与它们两个都有交集，如下图所示：</p>
<p><img src="http://wx4.sinaimg.cn/large/63503acbly1fmnd8rfet4j20ka0imdgo.jpg"></p>
<p>那么事件B的概率可以表示为$P(B) = P({B}\bigcap{A}) + P({B}\bigcap{C})$</p>
<p>通过条件概率，可以推断出$P({B}\bigcap{A}) = P(B|A)P(A)$，所以$P(B) = P(B|A)P(A) + P(B|C)P(C)$</p>
<p>这就是全概率公式，即事件B的概率等于事件A与事件C的概率分别乘以B对这两个事件的条件概率之和。</p>
<p>同样举个栗子来应用这个公式，假设有两家工厂生产并对外提供电灯泡，工厂X生产的电灯泡在99%的情况下能够工作超过5000小时，工厂Y生产的电灯泡在95%的情况下能够工作超过5000小时。工厂X在市场的占有率为60%，工厂Y为40%，如何推测出购买的灯泡的工作时间超过5000小时的概率是多少呢？</p>
<p>运用全概率公式，可以得出：<br>$$<br>\begin{equation}\begin{split}<br>Pr(A) &amp;=Pr(A | B_x) \cdot Pr(B_x) + Pr(A|B_y) \cdot Pr(B_y)\<br>&amp;= \frac{99}{100} \cdot \frac{6}{10} + \frac{95}{100} \cdot \frac{4}{10}\<br>&amp;= \frac{594 + 380}{1000}\<br>&amp;= \frac{974}{1000}<br>\end{split}\end{equation}<br>$$</p>
<ul>
<li><p>$Pr(B_x) = \frac{6}{10}$：购买到工厂X制造的电灯泡的概率。</p>
</li>
<li><p>$Pr(B_y) = \frac{4}{10}$：购买到工厂y制造的电灯泡的概率。</p>
</li>
<li><p>$Pr(A|B_x) = \frac{99}{100}$：工厂x制造的电灯泡工作时间超过5000小时的概率。</p>
</li>
<li><p>$Pr(A|B_y) = \frac{95}{100}$：工厂y制造的电灯泡工作时间超过5000小时的概率。</p>
</li>
</ul>
<p>因此，可以得知购买一个工作时间超过5000小时的电灯泡的概率为97.4%。</p>
<h3 id="贝叶斯定理"><a href="#贝叶斯定理" class="headerlink" title="贝叶斯定理"></a>贝叶斯定理</h3><hr>
<p>贝叶斯定理最早由英国数学家（同时也是神学家和哲学家）Thomas Bayes（1701-1761）提出，有趣的是他生前并没有发表过什么有关数学的学术文章，就连他最著名的成就贝叶斯定理也是由他的朋友Richard Price从他死后的遗物（笔记）中找到并发表的。</p>
<p>Thomas Bayes在晚年对概率学产生了兴趣，所谓的贝叶斯定理只是他生前为了解决一个逆概率问题（为了证明上帝是否存在，似乎哲学家们都很喜欢这个问题啊）所写的一篇文章。在那个时期，人们已经能够计算出正向概率问题，比方说，有一个袋子中有X个白球，Y个黑球，你伸手进去摸到黑球的概率是多少？这就是一个正向概率问题，而逆概率问题正好反过来，我们事先并不知道袋子中球的比例，而是不断伸手去摸好几个球，然后根据它们的颜色来推测黑球与白球的比例。</p>
<p>贝叶斯定理是关于随机事件A和B的条件概率的一则定理。通常，事件A在事件B（发生）的条件下的概率，与事件B在事件A（发生）的条件下的概率是不一样的，但它们两者之间是有确定的关系的，贝叶斯定理陈述了这个关系。</p>
<p>贝叶斯定理的一个主要应用为贝叶斯推理，它是一种建立在主观判断基础之上的推理方法，也就是说，你只需要先预估一个值，然后再去根据实际结果去不断修正，不需要任何客观因素。这种推理方式需要大量的计算，因此一直遭到其他人的诟病，无法得到广泛的应用，直到计算机的高速发展，并且人们发现很多事情都是无法事先进行客观判断的，因此贝叶斯推理才得以东山再起。</p>
<p>说了这么多理论知识（很多数学理论都像是在说绕口令），让我们来看一看公式吧，其实只需要把我们在上面推导出的条件概率公式继续进行推理，就可以得出贝叶斯公式。</p>
<p>$$P(A|B) = \frac{P(B|A)P(A)}{P(B)}$$</p>
<ul>
<li><p>$P(A|B)$：在B条件下的事件A的概率，在贝叶斯定理中，条件概率也被称为后验概率，即在事件B发生之后，我们对事件A概率的重新评估。</p>
</li>
<li><p>$P(B|A)$：在A条件下的事件B的概率，与上一条同理。</p>
</li>
<li><p>$P(A)$与$P(B)$被称为先验概率（也被称为边缘概率），即在事件B发生之前，我们对事件A概率的一个推断（不考虑任何事件B方面的因素），后面同理。</p>
</li>
<li><p>$\frac{P(B|A)}{P(B)}$被称为标准相似度，它是一个调整因子，主要是为了保证预测概率更接近真实概率。</p>
</li>
<li><p>根据这些术语，贝叶斯定理表述为： 后验概率 = 标准相似度 * 先验概率。</p>
</li>
</ul>
<p>让我们以著名的假阳性问题为例，假设某种疾病的发病率为0.001（1000个人中会有一个人得病），现有一种试剂在患者确实得病的情况下，有99%的几率呈现为阳性，而在患者没有得病的情况下，它有5%的几率呈现为阳性（也就是假阳性），如有一位病人的检验成果为阳性，那么他的得病概率是多少呢？</p>
<p>代入贝叶斯定理，假定事件A表示为得病的概率（<code>P(A) = 0.001</code>），这是我们的先验概率，它是在病人在实际注射试剂（缺乏实验的结果）之前预计的发病率，再假定事件B为试剂结果为阳性的概率，我们需要计算的是条件概率<code>P(A|B)</code>，即在事件B条件下的A概率，这就是后验概率，也就是病人在注射试剂之后（得到实验结果）得出的发病率。</p>
<p>由于还有未得病的概率，所以还需要假设事件C为未得病的先验概率（<code>P(C) = 1 - 0.001 = 0.999</code>），那么<code>P(B|C)</code>后验概率表示的是未得病条件下的试剂结果为阳性的概率，之后再代入全概率公式就可得出最终结果。</p>
<p>$$<br>\begin{equation}\begin{split}<br>P(A|B)&amp;=\frac{P(B|A)P(A)}{P(B)}\<br>&amp;= \frac{P(B|A)P(A)}{P(B|A)P(A) + P(B|C)P(C)}\<br>&amp;= \frac{0.99 \times 0.001}{0.99 \times 0.001 + 0.05 \times 0.999}\approx 0.019<br>\end{split}\end{equation}<br>$$</p>
<p>最终结果约等于2%，即使一个病人的试剂结果为阳性，他的患病几率也只有2%而已。</p>
<h3 id="朴素贝叶斯的概率模型"><a href="#朴素贝叶斯的概率模型" class="headerlink" title="朴素贝叶斯的概率模型"></a>朴素贝叶斯的概率模型</h3><hr>
<p>我们设一个待分类项$X = {f_1,f_2,\cdots,f_n}$，其中每个<code>f</code>为<code>X</code>的一个特征属性，然后设一个类别集合$C_1,C_2,\cdots,C_m$。</p>
<p>然后需要计算$P(C_1|X),P(C_2|X),\cdots,P(C_m|X)$，我们可以根据一个训练样本集合（已知分类的待分类项集合），然后统计得到在各类别下各个特征属性的条件概率：</p>
<p>$P(f_1|C_1),P(f_2|C_1),\cdots,P(f_n|C_1),\cdots,P(f_1|C_2),P(f_2|C_2),\cdots,P(f_n|C_2),\cdots,P(f_1|C_m),P(f_2|C_m),\cdots,P(f_n|C_m)$</p>
<p>如果$P(C_k|X) = MAX(P(C_1|X),P(C_2|X),\cdots,P(C_m|X))$，则${X}\in{C_k}$（贝叶斯分类其实就是取概率最大的那一个）。</p>
<p>朴素贝叶斯会假设每个特征都是独立的，根据贝叶斯定理可推得：$P(C_i|X) = \frac{P(X|C_i)P(C_i)}{P(X)}$，由于分母对于所有类别为常数，因此只需要将分子最大化即可，又因为各特征是互相独立的，所以最终推得：</p>
<p><img src="http://wx4.sinaimg.cn/large/63503acbly1fmrz54fusig20ge01hdfm.gif"></p>
<p>根据上述的公式推导，朴素贝叶斯的流程可如下图所示：</p>
<p><img src="http://wx3.sinaimg.cn/large/63503acbly1fmpnscvyl1j20np0sfaeh.jpg"></p>
<p>接下来我们通过一个案例来过一遍上图的流程。</p>
<p>现有一网站想要通过程序自动识别出账号的真实性（将账号分类为真实账号与不真实账号，所谓不真实账号即带有虚假信息或恶意注册的小号）。</p>
<ul>
<li><p> 首先需要确定特征属性和类别，然后获取训练样本。假设一个账号具有三个特征：日志数量/注册天数（<code>F1</code>）、好友数量/注册天数（<code>F2</code>）、是否使用了真实的头像（True为1，False为0）。</p>
</li>
<li><p> 该网站使用曾经人工检测过的10000个账号作为训练样本，那么计算每个类别的概率为$P(C_0) = 8900 \div 10000 = 0.89, P(C_1) = 1100 \div 10000 = 0.11$，<code>C0</code>为真实账号的类别概率也就是89%，<code>C1</code>为虚假账号的类别概率也就是11%。</p>
</li>
<li><p> 之后需要计算每个类别下的各个特征的条件概率，代入朴素贝叶斯分类器，可得$P(F_1|C)P(F_2|C)P(F_3|C)P(C)$，不过有一个问题是，<code>F1</code>与<code>F2</code>是连续变量，不适宜按照某个特定值计算概率。解决方法为将连续值转化为离散值，然后计算区间的概率，比如将<code>F1</code>分解为<code>[0,0.05]、[0.05,0.2]、[0.2,+∞]</code>三个区间，然后计算每个区间的概率即可。</p>
</li>
<li><p>已知某一账号的数据如下：$F_1 = 0.1,F_2 = 0.2,F_3 = 0$，推测该账号是真实账号还是虚假账号。在此例中，<code>F1</code>为0.1，落在第二个区间内，所以在计算的时候，就使用第二个区间的发生概率。根据训练样本可得出结果为：</p>
</li>
</ul>
<p>$$<br>\begin{equation}\begin{split}<br>P(F_1|C_0) = 0.5, P(F_1|C_1) = 0.1\<br>P(F_2|C_0) = 0.7, P(F_2|C_1) = 0.2\<br>P(F_3|C_0) = 0.2, P(F_3|C_1) = 0.9<br>\end{split}\end{equation}<br>$$</p>
<ul>
<li>接下来使用训练后的分类器可得出该账号的真实账号概率与虚假账号概率，然后取最大概率作为它的类别：</li>
</ul>
<p>$$<br>\begin{equation}\begin{split}<br>P(F_1|C_0)P(F_2|C_0)P(F_3|C_0)P(C_0) &amp;= 0.5 \times 0.7 \times 0.2 \times 0.89\<br>&amp;= 0.0623<br>\end{split}\end{equation}<br>$$<br>$$<br>\begin{equation}\begin{split}<br>P(F_1|C_1)P(F_2|C_1)P(F_3|C_1)P(C_1) &amp;= 0.1 \times 0.2 \times 0.9 \times 0.11\<br>&amp;= 0.00198<br>\end{split}\end{equation}<br>$$</p>
<p>   最终结果为该账号是一个真实账号。</p>
<h3 id="朴素贝叶斯的算法模型"><a href="#朴素贝叶斯的算法模型" class="headerlink" title="朴素贝叶斯的算法模型"></a>朴素贝叶斯的算法模型</h3><hr>
<p>在朴素贝叶斯中含有以下三种算法模型：</p>
<ul>
<li><p>Gaussian Naive Bayes：适合在特征变量具有连续性的时候使用，同时它还假设特征遵从于高斯分布（正态分布）。举个栗子，假设我们有一组人体特征的统计资料，该数据中的特征：身高、体重和脚掌长度等都为连续变量，很明显我们不能采用离散变量的方法来计算概率，由于样本太少，也无法分成区间计算，那么要怎么办呢？解决方法是假设特征项都是正态分布，然后通过样本计算出均值与标准差，这样就得到了正态分布的密度函数，有了密度函数，就可以代入值，进而算出某一点的密度函数的值。</p>
</li>
<li><p>MultiNomial Naive Bayes：与Gaussian Naive Bayes相反，多项式模型更适合处理特征是离散变量的情况，该模型会在计算先验概率$P(C_m)$和条件概率$P(F_n|C_m)$时会做一些平滑处理。具体公式为<img src="http://wx1.sinaimg.cn/large/63503acbly1fmrz54ux0tg20480130nd.gif">，其中<code>T</code>为总的样本数，<code>m</code>为总类别数，$T_{cm}$即类别为$C_m$的样本个数，<code>a</code>是一个平滑值。条件概率的公式为<img src="http://wx1.sinaimg.cn/large/63503acbly1fmrz55ag8ng205j0130sh.gif">，<code>n</code>为特征的个数，<code>T_cmfn</code>为类别为<code>C_m</code>特征为<code>F_n</code>的样本个数。当平滑值<code>a = 1</code>与<code>0 &lt; a &lt; 1</code>时，被称作为<code>Laplace</code>平滑，当<code>a = 0</code>时不做平滑。它的思想其实就是对每类别下所有划分的计数加1，这样如果训练样本数量足够大时，就不会对结果产生影响，并且解决了$P(F|C)$的频率为0的现象（某个类别下的某个特征划分没有出现，这会严重影响分类器的质量）。</p>
</li>
<li><p>Bernoulli Naive Bayes：Bernoulli适用于在特征属性为二进制的场景下，它对每个特征的取值是基于布尔值的，一个典型例子就是判断单词有没有在文本中出现。</p>
</li>
</ul>
<h3 id="朴素贝叶斯的实现"><a href="#朴素贝叶斯的实现" class="headerlink" title="朴素贝叶斯的实现"></a>朴素贝叶斯的实现</h3><hr>
<p>了解了足够多的理论，接下来我们要动手使用python来实现一个Gaussian Naive Bayes，目的是解决皮马人（一个印第安人部落）的糖尿病问题，<a target="_blank" rel="noopener" href="https://archive.ics.uci.edu/ml/machine-learning-databases/pima-indians-diabetes/pima-indians-diabetes.data">样本数据（请从该超链接中获取）</a>是一个csv格式的文件，每个值都是一个数字，该文件描述了从患者的年龄、怀孕次数和验血结果等方面的即时测量数据。每个记录都有一个类别值（一个布尔值，以0或1表示），该值表述了患者是否在五年内得过糖尿病。这是一个在机器学习文献中被大量研究过的数据集，一个比较好的预测精度应该在70%~76%。样本数据的每列含义如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">列1：怀孕次数</span><br><span class="line">列2：在口服葡萄糖耐量试验中，血浆葡萄糖的浓度（2小时）</span><br><span class="line">列3：心脏的舒张压（(mm Hg)）</span><br><span class="line">列4：肱三头肌皮肤褶皱厚度（mm）</span><br><span class="line">列5：二小时内的血清胰岛素（mu U&#x2F;ml）</span><br><span class="line">列6：体质指数 （(weight in kg&#x2F;(height in m)^2)）</span><br><span class="line">列7：糖尿病家族作用</span><br><span class="line">列8：年龄</span><br><span class="line">列9：类别布尔值，0为5年没得过糖尿病，1为5年内得过糖尿病</span><br><span class="line">------------------------------------</span><br><span class="line">6,148,72,35,0,33.6,0.627,50,1</span><br><span class="line">1,85,66,29,0,26.6,0.351,31,0</span><br><span class="line">8,183,64,0,0,23.3,0.672,32,1</span><br><span class="line">1,89,66,23,94,28.1,0.167,21,0</span><br><span class="line">0,137,40,35,168,43.1,2.288,33,1</span><br><span class="line">.........</span><br></pre></td></tr></table></figure>
<p>首先要做的是读取这个csv文件，并解析成我们可以直接使用的数据结构。由于样本数据文件中没有任何的空行和标记符号，每行都是对应的一行数据，只需要简单地把每一行封装到一个list中即可（返回结果为一个list，它的每一项元素都是包含一行数据的list），注意该文件中的数据都为数字，需要先做类型转换。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> csv</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">load_csv_file</span>(<span class="params">filename</span>):</span></span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(filename) <span class="keyword">as</span> f:</span><br><span class="line">        lines = csv.reader(f)</span><br><span class="line">        data_set = <span class="built_in">list</span>(lines)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(data_set)):</span><br><span class="line">        data_set[i] = [<span class="built_in">float</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> data_set[i]]</span><br><span class="line">    <span class="keyword">return</span> data_set</span><br></pre></td></tr></table></figure>
<p>获得了样本数据后，为了评估模型的准确性还需要将它切分为训练数据集（朴素贝叶斯需要使用它来进行预测）与测试数据集。数据在切分过程中是随机选取的，但我们会选择一个比率来控制训练数据集与测试数据集的大小，一般为67%：33%，这是一个比较常见的比率。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> random</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">split_data_set</span>(<span class="params">data_set, split_ratio</span>):</span></span><br><span class="line">    train_size = <span class="built_in">int</span>(<span class="built_in">len</span>(data_set) * split_ratio)</span><br><span class="line">    train_set = []</span><br><span class="line">    data_set_copy = <span class="built_in">list</span>(data_set)</span><br><span class="line">    <span class="keyword">while</span> <span class="built_in">len</span>(train_set) &lt; train_size:</span><br><span class="line">        index = random.randrange(<span class="built_in">len</span>(data_set_copy))</span><br><span class="line">        train_set.append(data_set_copy.pop(index))</span><br><span class="line">    <span class="keyword">return</span> [train_set, data_set_copy]</span><br></pre></td></tr></table></figure>
<p>切分了样本数据后，还要对训练数据集进行更细致的处理，由于Gaussian Naive Bayes假设了每个特征都遵循正态分布，所以需要从训练数据集中抽取出摘要，它包含了均值与标准差，摘要的数量由类别和特征属性的组合数决定，例如，如果有3个类别与7个特征属性，那么就需要对每个特征属性和类别计算出均值和标准差，这就是21个摘要。</p>
<p>在计算训练数据集的摘要之前，我们的第一个任务是要将训练数据集中的特征与类别进行分离，也就是说，构造出一个<code>key</code>为类别，值为所属该类别的数据行的散列表。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">separate_by_class</span>(<span class="params">data_set, class_index</span>):</span></span><br><span class="line">    result = &#123;&#125;</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(data_set)):</span><br><span class="line">        vector = data_set[i]</span><br><span class="line">        class_val = vector[class_index]</span><br><span class="line">        <span class="keyword">if</span> (class_val <span class="keyword">not</span> <span class="keyword">in</span> result):</span><br><span class="line">            result[class_val] = []</span><br><span class="line">        result[class_val].append(vector)</span><br><span class="line">    <span class="keyword">return</span> result</span><br></pre></td></tr></table></figure>
<p>由于已经知道了类别只有一个，而且在每行数据的最后一个，所以只需要将-1传入到class_index参数即可。然后就是计算训练数据集的摘要（每个类别中的每个特征属性的均值与标准差），均值会被作为正态分布的中间值，而标准差则描述了数据的离散程度，在计算概率时，它会被作为正态分布中每个特征属性的期望分布。</p>
<p>标准差就是方差的平方根，只要先求出方差（每个特征值与平均值的差的平方之和的平均值）就可以得出标准差。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> math</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mean</span>(<span class="params">numbers</span>):</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">sum</span>(numbers) / <span class="built_in">float</span>(<span class="built_in">len</span>(numbers))</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">stdev</span>(<span class="params">numbers</span>):</span></span><br><span class="line">    avg = mean(numbers)</span><br><span class="line">    variance = <span class="built_in">sum</span>([<span class="built_in">pow</span>(x - avg, <span class="number">2</span>) <span class="keyword">for</span> x <span class="keyword">in</span> numbers]) / <span class="built_in">float</span>(<span class="built_in">len</span>(numbers))</span><br><span class="line">    <span class="keyword">return</span> math.sqrt(variance)    </span><br></pre></td></tr></table></figure>
<p>有了这些辅助函数，计算摘要就很简单了，具体步骤就是先从训练数据集中构造出<code>key</code>为类别的散列表，然后根据类别与每个特征进行计算求出均值与标准差即可。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">summarize</span>(<span class="params">data_set</span>):</span></span><br><span class="line">    <span class="comment"># 使用zip函数将每个元素中的第n个属性封装为一个元组</span></span><br><span class="line">	<span class="comment"># 简单地说，就是把每列（特征）都打包到一个元组中</span></span><br><span class="line">    summaries = [(mean(feature), stdev(feature)) <span class="keyword">for</span> feature <span class="keyword">in</span> <span class="built_in">zip</span>(*data_set)]</span><br><span class="line">    <span class="keyword">del</span> summaries[-<span class="number">1</span>] <span class="comment"># 最后一行是类别与类别的摘要 所以删除</span></span><br><span class="line">    <span class="keyword">return</span> summaries</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">summarize_by_class</span>(<span class="params">data_set</span>):</span></span><br><span class="line">    class_map = separate_by_class(data_set, -<span class="number">1</span>)</span><br><span class="line">    summaries = &#123;&#125;</span><br><span class="line">    <span class="keyword">for</span> class_val, data <span class="keyword">in</span> class_map.items():</span><br><span class="line">        summaries[class_val] = summarize(data)</span><br><span class="line">    <span class="keyword">return</span> summaries</span><br></pre></td></tr></table></figure>
<p>数据的处理阶段已经完成了，下面的任务是要去根据训练数据集来进行预测，该阶段需要计算类概率与每个特征与类别的条件概率，然后选出概率最大的类别作为分类结果。关键在于计算条件概率，需要用到正态分布的密度函数，而它所依赖的参数（特征，均值，标准差）我们已经准备好了。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">calculate_probability</span>(<span class="params">x, mean, stdev</span>):</span></span><br><span class="line">    exponent = math.exp(-(math.<span class="built_in">pow</span>(x - mean, <span class="number">2</span>) / (<span class="number">2</span> * math.<span class="built_in">pow</span>(stdev, <span class="number">2</span>))))</span><br><span class="line">    <span class="keyword">return</span> (<span class="number">1</span> / (math.sqrt(<span class="number">2</span> * math.pi) * stdev)) * exponent</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">calculate_conditional_probabilities</span>(<span class="params">summaries, input_vector</span>):</span></span><br><span class="line">    probabilities = &#123;&#125;</span><br><span class="line">    <span class="keyword">for</span> class_val, class_summaries <span class="keyword">in</span> summaries.items():</span><br><span class="line">        probabilities[class_val] = <span class="number">1</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(class_summaries)):</span><br><span class="line">            mean, stdev = class_summaries[i]</span><br><span class="line">			<span class="comment"># input_vector是test_set的一行数据，x为该行中的某一特征属性</span></span><br><span class="line">            x = input_vector[i]</span><br><span class="line">			<span class="comment"># 将概率相乘</span></span><br><span class="line">            probabilities[class_val] *= calculate_probability(x, mean, stdev)</span><br><span class="line">    <span class="keyword">return</span> probabilities</span><br></pre></td></tr></table></figure>
<p>函数<code>calculate_conditional_probabilities()</code>返回了一个<code>key</code>为类别，值为其概率的散列表，这个散列表记录了每个特征类别的条件概率，之后只需要选出其中最大概率的类别即可。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">predict</span>(<span class="params">summaries, input_vector</span>):</span></span><br><span class="line">    probabilities = calculate_conditional_probabilities(summaries, input_vector)</span><br><span class="line">    best_label, best_prob = <span class="literal">None</span>, -<span class="number">1</span></span><br><span class="line">    <span class="keyword">for</span> class_val, probability <span class="keyword">in</span> probabilities.items():</span><br><span class="line">        <span class="keyword">if</span> best_label <span class="keyword">is</span> <span class="literal">None</span> <span class="keyword">or</span> probability &gt; best_prob:</span><br><span class="line">            best_label = class_val</span><br><span class="line">            best_prob = probability</span><br><span class="line">    <span class="keyword">return</span> best_label</span><br></pre></td></tr></table></figure>
<p>最后我们定义一个函数来对测试数据集中的每个数据实例进行预测以预估模型的准确性，该函数返回了一个预测值列表，包含了每个数据实例的预测值。根据这个返回值，就可以对预测结果进行准确性的评估了。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_predictions</span>(<span class="params">summaries, test_set</span>):</span></span><br><span class="line">    predictions = []</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(test_set)):</span><br><span class="line">        result = predict(summaries, test_set[i])</span><br><span class="line">        predictions.append(result)</span><br><span class="line">    <span class="keyword">return</span> predictions</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_accuracy</span>(<span class="params">predictions, test_set</span>):</span></span><br><span class="line">    correct = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(test_set)):</span><br><span class="line">		<span class="comment"># 分类结果与测试数据集一致，调整值自增</span></span><br><span class="line">        <span class="keyword">if</span> test_set[x][-<span class="number">1</span>] == predictions[x]:</span><br><span class="line">            correct += <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> (correct / <span class="built_in">float</span>(<span class="built_in">len</span>(test_set))) * <span class="number">100.0</span></span><br></pre></td></tr></table></figure>
<p>完整代码如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> csv, random, math</span><br><span class="line"></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">A simple classifier base on the gaussian naive bayes and</span></span><br><span class="line"><span class="string">problem of the pima indians diabetes.</span></span><br><span class="line"><span class="string">(https://archive.ics.uci.edu/ml/datasets/Pima+Indians+Diabetes)</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">load_csv_file</span>(<span class="params">filename</span>):</span></span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(filename) <span class="keyword">as</span> f:</span><br><span class="line">        lines = csv.reader(f)</span><br><span class="line">        data_set = <span class="built_in">list</span>(lines)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(data_set)):</span><br><span class="line">        data_set[i] = [<span class="built_in">float</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> data_set[i]]</span><br><span class="line">    <span class="keyword">return</span> data_set</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">split_data_set</span>(<span class="params">data_set, split_ratio</span>):</span></span><br><span class="line">    train_size = <span class="built_in">int</span>(<span class="built_in">len</span>(data_set) * split_ratio)</span><br><span class="line">    train_set = []</span><br><span class="line">    data_set_copy = <span class="built_in">list</span>(data_set)</span><br><span class="line">    <span class="keyword">while</span> <span class="built_in">len</span>(train_set) &lt; train_size:</span><br><span class="line">        index = random.randrange(<span class="built_in">len</span>(data_set_copy))</span><br><span class="line">        train_set.append(data_set_copy.pop(index))</span><br><span class="line">    <span class="keyword">return</span> [train_set, data_set_copy]</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">separate_by_class</span>(<span class="params">data_set, class_index</span>):</span></span><br><span class="line">    result = &#123;&#125;</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(data_set)):</span><br><span class="line">        vector = data_set[i]</span><br><span class="line">        class_val = vector[class_index]</span><br><span class="line">        <span class="keyword">if</span> (class_val <span class="keyword">not</span> <span class="keyword">in</span> result):</span><br><span class="line">            result[class_val] = []</span><br><span class="line">        result[class_val].append(vector)</span><br><span class="line">    <span class="keyword">return</span> result</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">mean</span>(<span class="params">numbers</span>):</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">sum</span>(numbers) / <span class="built_in">float</span>(<span class="built_in">len</span>(numbers))</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">stdev</span>(<span class="params">numbers</span>):</span></span><br><span class="line">    avg = mean(numbers)</span><br><span class="line">    variance = <span class="built_in">sum</span>([<span class="built_in">pow</span>(x - avg, <span class="number">2</span>) <span class="keyword">for</span> x <span class="keyword">in</span> numbers]) / <span class="built_in">float</span>(<span class="built_in">len</span>(numbers))</span><br><span class="line">    <span class="keyword">return</span> math.sqrt(variance)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">summarize</span>(<span class="params">data_set</span>):</span></span><br><span class="line">    summaries = [(mean(feature), stdev(feature)) <span class="keyword">for</span> feature <span class="keyword">in</span> <span class="built_in">zip</span>(*data_set)]</span><br><span class="line">    <span class="keyword">del</span> summaries[-<span class="number">1</span>]</span><br><span class="line">    <span class="keyword">return</span> summaries</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">summarize_by_class</span>(<span class="params">data_set</span>):</span></span><br><span class="line">    class_map = separate_by_class(data_set, -<span class="number">1</span>)</span><br><span class="line">    summaries = &#123;&#125;</span><br><span class="line">    <span class="keyword">for</span> class_val, data <span class="keyword">in</span> class_map.items():</span><br><span class="line">        summaries[class_val] = summarize(data)</span><br><span class="line">    <span class="keyword">return</span> summaries</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">calculate_probability</span>(<span class="params">x, mean, stdev</span>):</span></span><br><span class="line">    exponent = math.exp(-(math.<span class="built_in">pow</span>(x - mean, <span class="number">2</span>) / (<span class="number">2</span> * math.<span class="built_in">pow</span>(stdev, <span class="number">2</span>))))</span><br><span class="line">    <span class="keyword">return</span> (<span class="number">1</span> / (math.sqrt(<span class="number">2</span> * math.pi) * stdev)) * exponent</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">calculate_conditional_probabilities</span>(<span class="params">summaries, input_vector</span>):</span></span><br><span class="line">    probabilities = &#123;&#125;</span><br><span class="line">    <span class="keyword">for</span> class_val, class_summaries <span class="keyword">in</span> summaries.items():</span><br><span class="line">        probabilities[class_val] = <span class="number">1</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(class_summaries)):</span><br><span class="line">            mean, stdev = class_summaries[i]</span><br><span class="line">            x = input_vector[i]</span><br><span class="line">            probabilities[class_val] *= calculate_probability(x, mean, stdev)</span><br><span class="line">    <span class="keyword">return</span> probabilities</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">predict</span>(<span class="params">summaries, input_vector</span>):</span></span><br><span class="line">    probabilities = calculate_conditional_probabilities(summaries, input_vector)</span><br><span class="line">    best_label, best_prob = <span class="literal">None</span>, -<span class="number">1</span></span><br><span class="line">    <span class="keyword">for</span> class_val, probability <span class="keyword">in</span> probabilities.items():</span><br><span class="line">        <span class="keyword">if</span> best_label <span class="keyword">is</span> <span class="literal">None</span> <span class="keyword">or</span> probability &gt; best_prob:</span><br><span class="line">            best_label = class_val</span><br><span class="line">            best_prob = probability</span><br><span class="line">    <span class="keyword">return</span> best_label</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_predictions</span>(<span class="params">summaries, test_set</span>):</span></span><br><span class="line">    predictions = []</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(test_set)):</span><br><span class="line">        result = predict(summaries, test_set[i])</span><br><span class="line">        predictions.append(result)</span><br><span class="line">    <span class="keyword">return</span> predictions</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_accuracy</span>(<span class="params">predictions, test_set</span>):</span></span><br><span class="line">    correct = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(test_set)):</span><br><span class="line">        <span class="keyword">if</span> test_set[x][-<span class="number">1</span>] == predictions[x]:</span><br><span class="line">            correct += <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> (correct / <span class="built_in">float</span>(<span class="built_in">len</span>(test_set))) * <span class="number">100.0</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">main</span>():</span></span><br><span class="line">    filename = <span class="string">&#x27;pima-indians-diabetes.data.csv&#x27;</span></span><br><span class="line">    split_ratio = <span class="number">0.67</span></span><br><span class="line">    data_set = load_csv_file(filename)</span><br><span class="line">    train_set, test_set = split_data_set(data_set, split_ratio)</span><br><span class="line">    print(<span class="string">&#x27;Split %s rows into train set = %s and test set = %s rows&#x27;</span></span><br><span class="line">                %(<span class="built_in">len</span>(data_set), <span class="built_in">len</span>(train_set), <span class="built_in">len</span>(test_set)))</span><br><span class="line">    <span class="comment"># prepare model</span></span><br><span class="line">    summaries = summarize_by_class(train_set)</span><br><span class="line">    <span class="comment"># predict and test</span></span><br><span class="line">    predictions = get_predictions(summaries, test_set)</span><br><span class="line">    accuracy = get_accuracy(predictions, test_set)</span><br><span class="line">    print(<span class="string">&#x27;Accuracy: %s&#x27;</span> % accuracy)</span><br><span class="line"></span><br><span class="line">main()</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h3><hr>
<ul>
<li><p><a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Bayes%27_theorem">Bayes’ theorem - Wikipedia</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Conditional_probability">Conditional probability - Wikipedia</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Law_of_total_probability">Law of total probability - Wikipedia</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Naive_Bayes_classifier">Naive Bayes classifier - Wikipedia</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://www.analyticsvidhya.com/blog/2017/09/naive-bayes-explained/"> 6 Easy Steps to Learn Naive Bayes Algorithm (with code in Python)</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://dataaspirant.com/2017/02/06/naive-bayes-classifier-machine-learning/">How the Naive Bayes Classifier works in Machine Learning</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://machinelearningmastery.com/naive-bayes-classifier-scratch-python/">Naive Bayes Classifier From Scratch in Python</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="http://mindhacks.cn/2008/09/21/the-magical-bayesian-method/">数学之美番外篇：平凡而又神奇的贝叶斯方法 – 刘未鹏 | Mind Hacks</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="http://www.ruanyifeng.com/blog/2013/12/naive_bayes_classifier.html">朴素贝叶斯分类器的应用 - 阮一峰的网络日志</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="http://www.cnblogs.com/leoo2sk/archive/2010/09/17/1829190.html">算法杂货铺——分类算法之朴素贝叶斯分类(Naive Bayesian classification) - T2噬菌体 - 博客园</a></p>
</li>
</ul>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh">
    <link itemprop="mainEntityOfPage" href="https://suyuhuan.gitee.io/yuwanzi.io/2017/11/30/2017-11-30-netty_introduction/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/yuwanzi.io/images/avatar.gif">
      <meta itemprop="name" content="玉丸子">
      <meta itemprop="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="玉丸子 | Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/yuwanzi.io/2017/11/30/2017-11-30-netty_introduction/" class="post-title-link" itemprop="url">Netty的那点事儿</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Veröffentlicht am</span>

      <time title="Erstellt: 2017-11-30 18:00:00" itemprop="dateCreated datePublished" datetime="2017-11-30T18:00:00+08:00">2017-11-30</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">Bearbeitet am</span>
        <time title="Geändert am: 2020-11-07 08:58:17" itemprop="dateModified" datetime="2020-11-07T08:58:17+08:00">2020-11-07</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">in</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%90%8E%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">后端</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%90%8E%E7%AB%AF/Java/" itemprop="url" rel="index"><span itemprop="name">Java</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%90%8E%E7%AB%AF/Java/Netty/" itemprop="url" rel="index"><span itemprop="name">Netty</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <p><img src="https://netty.io/images/components.png"></p>
<p>Netty是一个基于异步与事件驱动的网络应用程序框架，它支持快速与简单地开发可维护的高性能的服务器与客户端。</p>
<p>所谓事件驱动就是由通过各种事件响应来决定程序的流程，在Netty中到处都充满了异步与事件驱动，这种特点使得应用程序可以以任意的顺序响应在任意的时间点产生的事件，它带来了非常高的可伸缩性，让你的应用可以在需要处理的工作不断增长时，通过某种可行的方式或者扩大它的处理能力来适应这种增长。</p>
<p>Netty提供了高性能与易用性，它具有以下特点：</p>
<ul>
<li><p>拥有设计良好且统一的API，支持NIO与OIO（阻塞IO）等多种传输类型，支持真正的无连接UDP Socket。</p>
</li>
<li><p>简单而强大的线程模型，可高度定制线程（池）。</p>
</li>
<li><p>良好的模块化与解耦，支持可扩展和灵活的事件模型，可以很轻松地分离关注点以复用逻辑组件（可插拔的）。</p>
</li>
<li><p>性能高效，拥有比Java核心API更高的吞吐量，通过zero-copy功能以实现最少的内存复制消耗。</p>
</li>
<li><p>内置了许多常用的协议编解码器，如HTTP、SSL、WebScoket等常见协议可以通过Netty做到开箱即用。用户也可以利用Netty简单方便地实现自己的应用层协议。</p>
</li>
</ul>
<p>大多数人使用Netty主要还是为了提高应用的性能，而高性能则离不开非阻塞IO。Netty的非阻塞IO是基于Java NIO的，并且对其进行了封装（直接使用Java NIO API在高复杂度下的应用中是一项非常繁琐且容易出错的操作，而Netty帮你封装了这些复杂操作）。</p>
<p>NIO可以称为New IO也可以称为Non-blocking IO，它比Java旧的阻塞IO在性能上要高效许多（如果让每一个连接中的IO操作都单独创建一个线程，那么阻塞IO并不会比NIO在性能上落后，但不可能创建无限多的线程，在连接数非常多的情况下会很糟糕）。</p>
<ul>
<li><p>ByteBuffer：NIO的数据传输是基于缓冲区的，ByteBuffer正是NIO数据传输中所使用的缓冲区抽象。ByteBuffer支持在堆外分配内存，并且尝试避免在执行I/O操作中的多余复制。一般的I/O操作都需要进行系统调用，这样会先切换到内核态，内核态要先从文件读取数据到它的缓冲区，只有等数据准备完毕后，才会从内核态把数据写到用户态，所谓的阻塞IO其实就是说的在等待数据准备好的这段时间内进行阻塞。如果想要避免这个额外的内核操作，可以通过使用mmap（虚拟内存映射）的方式来让用户态直接操作文件。</p>
</li>
<li><p>Channel：它类似于文件描述符，简单地来说它代表了一个实体（如一个硬件设备、文件、Socket或者一个能够执行一个或多个不同的I/O操作的程序组件）。你可以从一个Channel中读取数据到缓冲区，也可以将一个缓冲区中的数据写入到Channel。</p>
</li>
<li><p>Selector：选择器是NIO实现的关键，NIO采用的是I/O多路复用的方式来实现非阻塞，Selector通过在一个线程中监听每个Channel的IO事件来确定有哪些已经准备好进行IO操作的Channel，因此可以在任何时间检查任意的读操作或写操作的完成状态。这种方式避免了等待IO操作准备数据时的阻塞，使用较少的线程便可以处理许多连接，减少了线程切换与维护的开销。</p>
<p><img src="http://wx2.sinaimg.cn/large/63503acbly1flys7n7hvaj20h90doglj.jpg"></p>
</li>
</ul>
<p>了解了NIO的实现思想之后，我觉得还很有必要了解一下Unix中的I/O模型，Unix中拥有以下5种I/O模型：</p>
<ul>
<li><p>阻塞I/O（Blocking I/O）</p>
</li>
<li><p>非阻塞I/O（Non-blocking I/O）</p>
</li>
<li><p>I/O多路复用（I/O multiplexing (select and poll)）</p>
</li>
<li><p>信号驱动I/O（signal driven I/O (SIGIO)）</p>
</li>
<li><p>异步I/O（asynchronous I/O (the POSIX aio_functions)）</p>
</li>
</ul>
<p><img src="http://wx3.sinaimg.cn/large/63503acbly1flz1e7kzblj20wb0ftq3l.jpg" alt="阻塞I/O模型"></p>
<p>阻塞I/O模型是最常见的I/O模型，通常我们使用的InputStream/OutputStream都是基于阻塞I/O模型。在上图中，我们使用UDP作为例子，recvfrom()函数是UDP协议用于接收数据的函数，它需要使用系统调用并一直阻塞到内核将数据准备好，之后再由内核缓冲区复制数据到用户态（即是recvfrom()接收到数据），所谓阻塞就是在等待内核准备数据的这段时间内什么也不干。</p>
<p>举个生活中的例子，阻塞I/O就像是你去餐厅吃饭，在等待饭做好的时间段中，你只能在餐厅中坐着干等（如果你在玩手机那么这就是非阻塞I/O了）。</p>
<p><img src="http://wx2.sinaimg.cn/large/63503acbly1flz1e8lh7rj20wb0ft0ty.jpg" alt="非阻塞I/O模型"></p>
<p>在非阻塞I/O模型中，内核在数据尚未准备好的情况下回返回一个错误码<code>EWOULDBLOCK</code>，而recvfrom并没有在失败的情况下选择阻塞休眠，而是不断地向内核询问是否已经准备完毕，在上图中，前三次内核都返回了<code>EWOULDBLOCK</code>，直到第四次询问时，内核数据准备完毕，然后开始将内核中缓存的数据复制到用户态。这种不断询问内核以查看某种状态是否完成的方式被称为<code>polling（轮询）</code>。</p>
<p>非阻塞I/O就像是你在点外卖，只不过你非常心急，每隔一段时间就要打电话问外卖小哥有没有到。</p>
<p><img src="http://wx3.sinaimg.cn/large/63503acbly1flz1e989dfj20wh0g80tw.jpg" alt="I/O多路复用模型"></p>
<p>I/O多路复用的思想跟非阻塞I/O是一样的，只不过在非阻塞I/O中，是在recvfrom的用户态（或一个线程）中去轮询内核，这种方式会消耗大量的CPU时间。而I/O多路复用则是通过select()或poll()系统调用来负责进行轮询，以实现监听I/O读写事件的状态。如上图中，select监听到一个datagram可读时，就交由recvfrom去发送系统调用将内核中的数据复制到用户态。</p>
<p>这种方式的优点很明显，通过I/O多路复用可以监听多个文件描述符，且在内核中完成监控的任务。但缺点是至少需要两个系统调用（select()与recvfrom()）。</p>
<p>I/O多路复用同样适用于点外卖这个例子，只不过你在等外卖的期间完全可以做自己的事情，当外卖到的时候会通过外卖APP或者由外卖小哥打电话来通知你。</p>
<p>Unix中提供了两种I/O多路复用函数，select()和poll()。select()的兼容性更好，但它在单个进程中所能监控的文件描述符是有限的，这个值与<code>FD_SETSIZE</code>相关，32位系统中默认为1024，64位系统中为2048。select()还有一个缺点就是他轮询的方式，它采取了线性扫描的轮询方式，每次都要遍历FD_SETSIZE个文件描述符，不管它们是否活不活跃的。poll()本质上与select()的实现没有区别，不过在数据结构上区别很大，用户必须分配一个pollfd结构数组，该数组维护在内核态中，正因如此，poll()并不像select()那样拥有大小上限的限制，但缺点同样也很明显，大量的fd数组会在用户态与内核态之间不断复制，不管这样的复制是否有意义。</p>
<p>还有一种比select()与poll()更加高效的实现叫做epoll()，它是由Linux内核2.6推出的可伸缩的I/O多路复用实现，目的是为了替代select()与poll()。epoll()同样没有文件描述符上限的限制，它使用一个文件描述符来管理多个文件描述符，并使用一个红黑树来作为存储结构。同时它还支持边缘触发（edge-triggered）与水平触发（level-triggered）两种模式（poll()只支持水平触发），在边缘触发模式下，<code>epoll_wait</code>仅会在新的事件对象首次被加入到epoll时返回，而在水平触发模式下，<code>epoll_wait</code>会在事件状态未变更前不断地触发。也就是说，边缘触发模式只会在文件描述符变为就绪状态时通知一次，水平触发模式会不断地通知该文件描述符直到被处理。</p>
<p>关于<code>epoll_wait</code>请参考如下epoll API。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个epoll对象并返回它的文件描述符。</span></span><br><span class="line"><span class="comment">// 参数flags允许修改epoll的行为，它只有一个有效值EPOLL_CLOEXEC。</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">epoll_create1</span><span class="params">(<span class="keyword">int</span> flags)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 配置对象，该对象负责描述监控哪些文件描述符和哪些事件。</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">epoll_ctl</span><span class="params">(<span class="keyword">int</span> epfd, <span class="keyword">int</span> op, <span class="keyword">int</span> fd, struct epoll_event *event)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等待与epoll_ctl注册的任何事件，直至事件发生一次或超时。</span></span><br><span class="line"><span class="comment">// 返回在events中发生的事件，最多同时返回maxevents个。</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">epoll_wait</span><span class="params">(<span class="keyword">int</span> epfd, struct epoll_event *events, <span class="keyword">int</span> maxevents, <span class="keyword">int</span> timeout)</span></span>;</span><br></pre></td></tr></table></figure>
<p>epoll另一亮点是采用了事件驱动的方式而不是轮询，在<code>epoll_ctl</code>中注册的文件描述符在事件触发的时候会通过一个回调机制来激活该文件描述符，<code>epoll_wait</code>便可以收到通知。这样效率就不会与文件描述符的数量成正比。epoll还采用了mmap来减少内核态与用户态之间的数据传输开销。</p>
<p>在Java NIO2（从JDK1.7开始引入）中，只要Linux内核版本在2.6以上，就会采用epoll，如下源码所示（DefaultSelectorProvider.java）。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SelectorProvider <span class="title">create</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    String osname = AccessController.doPrivileged(</span><br><span class="line">        <span class="keyword">new</span> GetPropertyAction(<span class="string">&quot;os.name&quot;</span>));</span><br><span class="line">    <span class="keyword">if</span> (<span class="string">&quot;SunOS&quot;</span>.equals(osname)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> sun.nio.ch.DevPollSelectorProvider();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// use EPollSelectorProvider for Linux kernels &gt;= 2.6</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="string">&quot;Linux&quot;</span>.equals(osname)) &#123;</span><br><span class="line">        String osversion = AccessController.doPrivileged(</span><br><span class="line">            <span class="keyword">new</span> GetPropertyAction(<span class="string">&quot;os.version&quot;</span>));</span><br><span class="line">        String[] vers = osversion.split(<span class="string">&quot;\\.&quot;</span>, <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">if</span> (vers.length &gt;= <span class="number">2</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">int</span> major = Integer.parseInt(vers[<span class="number">0</span>]);</span><br><span class="line">                <span class="keyword">int</span> minor = Integer.parseInt(vers[<span class="number">1</span>]);</span><br><span class="line">                <span class="keyword">if</span> (major &gt; <span class="number">2</span> || (major == <span class="number">2</span> &amp;&amp; minor &gt;= <span class="number">6</span>)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">new</span> sun.nio.ch.EPollSelectorProvider();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (NumberFormatException x) &#123;</span><br><span class="line">                <span class="comment">// format not recognized</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> sun.nio.ch.PollSelectorProvider();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><img src="http://wx3.sinaimg.cn/large/63503acbly1flz1e9uk8aj20wb0ft3zn.jpg" alt="信号驱动I/O模型"></p>
<p>信号驱动I/O模型使用到了信号，内核在数据准备就绪时会通过信号来进行通知。我们首先开启了一个信号驱动I/O套接字，并使用sigaction系统调用来安装信号处理程序，内核直接返回，不会阻塞用户态。当datagram准备好时，内核会发送SIGIO信号，recvfrom接收到信号后会发送系统调用开始进行I/O操作。</p>
<p>这种模型的优点是主进程（线程）不会被阻塞，当数据准备就绪时，通过信号处理程序来通知主进程（线程）准备进行I/O操作与对数据的处理。</p>
<p><img src="http://wx2.sinaimg.cn/large/63503acbly1flz1eai66rj20wb0g8aau.jpg" alt="异步I/O模型"></p>
<p>我们之前讨论的各种I/O模型无论是阻塞还是非阻塞，它们所说的阻塞都是指的数据准备阶段。异步I/O模型同样依赖于信号处理程序来进行通知，但与以上I/O模型都不相同的是，异步I/O模型通知的是I/O操作已经完成，而不是数据准备完成。</p>
<p>可以说异步I/O模型才是真正的非阻塞，主进程只管做自己的事情，然后在I/O操作完成时调用回调函数来完成一些对数据的处理操作即可。</p>
<p>闲扯了这么多，想必大家已经对I/O模型有了一个深刻的认识。之后，我们将会结合部分源码（Netty4.X）来探讨Netty中的各大核心组件，以及如何使用Netty，你会发现实现一个Netty程序是多么简单（而且还伴随了高性能与可维护性）。</p>
<blockquote>
<p>本文作者为<a target="_blank" rel="noopener" href="https://github.com/SylvanasSun">SylvanasSun(sylvanas.sun@gmail.com)</a>，首发于<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/">SylvanasSun’s Blog</a>。<br>原文链接：<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introduction/">https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introduction/</a><br>（转载请务必保留本段声明，并且保留超链接。）</p>
</blockquote>
<h3 id="ByteBuf"><a href="#ByteBuf" class="headerlink" title="ByteBuf"></a>ByteBuf</h3><hr>
<p>网络传输的基本单位是字节，在Java NIO中提供了ByteBuffer作为字节缓冲区容器，但该类的API使用起来不太方便，所以Netty实现了ByteBuf作为其替代品，下面是使用ByteBuf的优点：</p>
<ul>
<li><p>相比ByteBuffer使用起来更加简单。</p>
</li>
<li><p>通过内置的复合缓冲区类型实现了透明的zero-copy。</p>
</li>
<li><p>容量可以按需增长。</p>
</li>
<li><p>读和写使用了不同的索引指针。</p>
</li>
<li><p>支持链式调用。</p>
</li>
<li><p>支持引用计数与池化。</p>
</li>
<li><p>可以被用户自定义的缓冲区类型扩展。</p>
</li>
</ul>
<p>在讨论ByteBuf之前，我们先需要了解一下ByteBuffer的实现，这样才能比较深刻地明白它们之间的区别。</p>
<p>ByteBuffer继承于<code>abstract class Buffer</code>（所以还有LongBuffer、IntBuffer等其他类型的实现），本质上它只是一个有限的线性的元素序列，包含了三个重要的属性。</p>
<ul>
<li><p>Capacity：缓冲区中元素的容量大小，你只能将capacity个数量的元素写入缓冲区，一旦缓冲区已满就需要清理缓冲区才能继续写数据。</p>
</li>
<li><p>Position：指向下一个写入数据位置的索引指针，初始位置为0，最大为capacity-1。当写模式转换为读模式时，position需要被重置为0。</p>
</li>
<li><p>Limit：在写模式中，limit是可以写入缓冲区的最大索引，也就是说它在写模式中等价于缓冲区的容量。在读模式中，limit表示可以读取数据的最大索引。</p>
</li>
</ul>
<p><img src="http://tutorials.jenkov.com/images/java-nio/buffers-modes.png"></p>
<p>由于Buffer中只维护了position一个索引指针，所以它在读写模式之间的切换需要调用一个flip()方法来重置指针。使用Buffer的流程一般如下：</p>
<ul>
<li><p>写入数据到缓冲区。</p>
</li>
<li><p>调用flip()方法。</p>
</li>
<li><p>从缓冲区中读取数据</p>
</li>
<li><p>调用buffer.clear()或者buffer.compact()清理缓冲区，以便下次写入数据。</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">RandomAccessFile aFile = <span class="keyword">new</span> RandomAccessFile(<span class="string">&quot;data/nio-data.txt&quot;</span>, <span class="string">&quot;rw&quot;</span>);</span><br><span class="line">FileChannel inChannel = aFile.getChannel();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 分配一个48字节大小的缓冲区</span></span><br><span class="line">ByteBuffer buf = ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> bytesRead = inChannel.read(buf); <span class="comment">// 读取数据到缓冲区</span></span><br><span class="line"><span class="keyword">while</span> (bytesRead != -<span class="number">1</span>) &#123;</span><br><span class="line"></span><br><span class="line">  buf.flip();  <span class="comment">// 将position重置为0</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span>(buf.hasRemaining())&#123;</span><br><span class="line">      System.out.print((<span class="keyword">char</span>) buf.get()); <span class="comment">// 读取数据并输出到控制台</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  buf.clear(); <span class="comment">// 清理缓冲区</span></span><br><span class="line">  bytesRead = inChannel.read(buf);</span><br><span class="line">&#125;</span><br><span class="line">aFile.close();</span><br></pre></td></tr></table></figure>
<p>Buffer中核心方法的实现也非常简单，主要就是在操作指针position。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">   <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Sets this buffer&#x27;s mark at its position.</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@return</span>  This buffer</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> Buffer <span class="title">mark</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       mark = position; <span class="comment">// mark属性是用来标记当前索引位置的</span></span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将当前索引位置重置为mark所标记的位置</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> Buffer <span class="title">reset</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       <span class="keyword">int</span> m = mark;</span><br><span class="line">       <span class="keyword">if</span> (m &lt; <span class="number">0</span>)</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> InvalidMarkException();</span><br><span class="line">       position = m;</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">   &#125;	</span><br><span class="line"></span><br><span class="line"><span class="comment">// 翻转这个Buffer，将limit设置为当前索引位置，然后再把position重置为0</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> Buffer <span class="title">flip</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       limit = position;</span><br><span class="line">       position = <span class="number">0</span>;</span><br><span class="line">       mark = -<span class="number">1</span>;</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 清理缓冲区</span></span><br><span class="line"><span class="comment">// 说是清理,也只是把postion与limit进行重置,之后再写入数据就会覆盖之前的数据了</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> Buffer <span class="title">clear</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       position = <span class="number">0</span>;</span><br><span class="line">       limit = capacity;</span><br><span class="line">       mark = -<span class="number">1</span>;</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 返回剩余空间</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">remaining</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       <span class="keyword">return</span> limit - position;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>
<p>Java NIO中的Buffer API操作的麻烦之处就在于读写转换需要手动重置指针。而ByteBuf没有这种繁琐性，它维护了两个不同的索引，一个用于读取，一个用于写入。当你从ByteBuf读取数据时，它的readerIndex将会被递增已经被读取的字节数，同样的，当你写入数据时，writerIndex则会递增。readerIndex的最大范围在writerIndex的所在位置，如果试图移动readerIndex超过该值则会触发异常。</p>
<p>ByteBuf中名称以read或write开头的方法将会递增它们其对应的索引，而名称以get或set开头的方法则不会。ByteBuf同样可以指定一个最大容量，试图移动writerIndex超过该值则会触发异常。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">byte</span> <span class="title">readByte</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       <span class="keyword">this</span>.checkReadableBytes0(<span class="number">1</span>); <span class="comment">// 检查readerIndex是否已越界</span></span><br><span class="line">       <span class="keyword">int</span> i = <span class="keyword">this</span>.readerIndex;</span><br><span class="line">       <span class="keyword">byte</span> b = <span class="keyword">this</span>._getByte(i);</span><br><span class="line">       <span class="keyword">this</span>.readerIndex = i + <span class="number">1</span>; <span class="comment">// 递增readerIndex</span></span><br><span class="line">       <span class="keyword">return</span> b;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">checkReadableBytes0</span><span class="params">(<span class="keyword">int</span> minimumReadableBytes)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">this</span>.ensureAccessible();</span><br><span class="line">       <span class="keyword">if</span>(<span class="keyword">this</span>.readerIndex &gt; <span class="keyword">this</span>.writerIndex - minimumReadableBytes) &#123;</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> IndexOutOfBoundsException(String.format(<span class="string">&quot;readerIndex(%d) + length(%d) exceeds 			  writerIndex(%d): %s&quot;</span>, <span class="keyword">new</span> Object[]&#123;Integer.valueOf(<span class="keyword">this</span>.readerIndex), Integer.valueOf(minimumReadableBytes), Integer.valueOf(<span class="keyword">this</span>.writerIndex), <span class="keyword">this</span>&#125;));</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> ByteBuf <span class="title">writeByte</span><span class="params">(<span class="keyword">int</span> value)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">this</span>.ensureAccessible();</span><br><span class="line">       <span class="keyword">this</span>.ensureWritable0(<span class="number">1</span>); <span class="comment">// 检查writerIndex是否会越过capacity</span></span><br><span class="line">       <span class="keyword">this</span>._setByte(<span class="keyword">this</span>.writerIndex++, value);</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">ensureWritable0</span><span class="params">(<span class="keyword">int</span> minWritableBytes)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">if</span>(minWritableBytes &gt; <span class="keyword">this</span>.writableBytes()) &#123;</span><br><span class="line">           <span class="keyword">if</span>(minWritableBytes &gt; <span class="keyword">this</span>.maxCapacity - <span class="keyword">this</span>.writerIndex) &#123;</span><br><span class="line">               <span class="keyword">throw</span> <span class="keyword">new</span> IndexOutOfBoundsException(String.format(<span class="string">&quot;writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s&quot;</span>, <span class="keyword">new</span> Object[]&#123;Integer.valueOf(<span class="keyword">this</span>.writerIndex), Integer.valueOf(minWritableBytes), Integer.valueOf(<span class="keyword">this</span>.maxCapacity), <span class="keyword">this</span>&#125;));</span><br><span class="line">           &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">               <span class="keyword">int</span> newCapacity = <span class="keyword">this</span>.alloc().calculateNewCapacity(<span class="keyword">this</span>.writerIndex + minWritableBytes, <span class="keyword">this</span>.maxCapacity);</span><br><span class="line">               <span class="keyword">this</span>.capacity(newCapacity);</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;	</span><br><span class="line"></span><br><span class="line"><span class="comment">// get与set只对传入的索引进行了检查，然后对其位置进行get或set	</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">byte</span> <span class="title">getByte</span><span class="params">(<span class="keyword">int</span> index)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">this</span>.checkIndex(index);</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">this</span>._getByte(index);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> ByteBuf <span class="title">setByte</span><span class="params">(<span class="keyword">int</span> index, <span class="keyword">int</span> value)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">this</span>.checkIndex(index);</span><br><span class="line">       <span class="keyword">this</span>._setByte(index, value);</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>
<p>ByteBuf同样支持在堆内和堆外进行分配。在堆内分配也被称为支撑数组模式，它能在没有使用池化的情况下提供快速的分配和释放。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ByteBuf heapBuf = Unpooled.copiedBuffer(bytes);</span><br><span class="line"><span class="keyword">if</span> (heapBuf.hasArray()) &#123; <span class="comment">// 判断是否有一个支撑数组</span></span><br><span class="line">	<span class="keyword">byte</span>[] array = heapBuf.array();</span><br><span class="line">	<span class="comment">// 计算第一个字节的偏移量</span></span><br><span class="line">	<span class="keyword">int</span> offset = heapBuf.arrayOffset() + heapBuf.readerIndex();</span><br><span class="line">	<span class="keyword">int</span> length = heapBuf.readableBytes(); <span class="comment">// 获得可读字节</span></span><br><span class="line">	handleArray(array,offset,length); <span class="comment">// 调用你的处理方法</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>另一种模式为堆外分配，Java NIO ByteBuffer类在JDK1.4时就已经允许JVM实现通过JNI调用来在堆外分配内存（调用malloc()函数在JVM堆外分配内存），这主要是为了避免额外的缓冲区复制操作。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ByteBuf directBuf = Unpooled.directBuffer(capacity);</span><br><span class="line"><span class="keyword">if</span> (!directBuf.hasArray()) &#123;</span><br><span class="line">	<span class="keyword">int</span> length = directBuf.readableBytes();</span><br><span class="line">	<span class="keyword">byte</span>[] array = <span class="keyword">new</span> <span class="keyword">byte</span>[length];</span><br><span class="line">	<span class="comment">// 将字节复制到数组中</span></span><br><span class="line">	directBuf.getBytes(directBuf.readerIndex(),array);</span><br><span class="line">	handleArray(array,<span class="number">0</span>,length);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>ByteBuf还支持第三种模式，它被称为复合缓冲区，为多个ByteBuf提供了一个聚合视图。在这个视图中，你可以根据需要添加或者删除ByteBuf实例，ByteBuf的子类CompositeByteBuf实现了该模式。</p>
<p>一个适合使用复合缓冲区的场景是HTTP协议，通过HTTP协议传输的消息都会被分成两部分——头部和主体，如果这两部分由应用程序的不同模块产生，将在消息发送时进行组装，并且该应用程序还会为多个消息复用相同的消息主体，这样对于每个消息都将会创建一个新的头部，产生了很多不必要的内存操作。使用CompositeByteBuf是一个很好的选择，它消除了这些额外的复制，以帮助你复用这些消息。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">CompositeByteBuf messageBuf = Unpooled.compositeBuffer();</span><br><span class="line">ByteBuf headerBuf = ....;</span><br><span class="line">ByteBuf bodyBuf = ....;</span><br><span class="line">messageBuf.addComponents(headerBuf,bodyBuf);</span><br><span class="line"><span class="keyword">for</span> (ByteBuf buf : messageBuf) &#123;</span><br><span class="line">	System.out.println(buf.toString());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>CompositeByteBuf透明的实现了zero-copy，zero-copy其实就是避免数据在两个内存区域中来回的复制。从操作系统层面上来讲，zero-copy指的是避免在内核态与用户态之间的数据缓冲区复制（通过mmap避免），而Netty中的zero-copy更偏向于在用户态中的数据操作的优化，就像使用CompositeByteBuf来复用多个ByteBuf以避免额外的复制，也可以使用wrap()方法来将一个字节数组包装成ByteBuf，又或者使用ByteBuf的slice()方法把它分割为多个共享同一内存区域的ByteBuf，这些都是为了优化内存的使用率。</p>
<p>那么如何创建ByteBuf呢？在上面的代码中使用到了Unpooled，它是Netty提供的一个用于创建与分配ByteBuf的工具类，建议都使用这个工具类来创建你的缓冲区，不要自己去调用构造函数。经常使用的是wrappedBuffer()与copiedBuffer()，它们一个是用于将一个字节数组或ByteBuffer包装为一个ByteBuf，一个是根据传入的字节数组与ByteBuffer/ByteBuf来复制出一个新的ByteBuf。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">   <span class="comment">// 通过array.clone()来复制一个数组进行包装</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ByteBuf <span class="title">copiedBuffer</span><span class="params">(<span class="keyword">byte</span>[] array)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">return</span> array.length == <span class="number">0</span>?EMPTY_BUFFER:wrappedBuffer((<span class="keyword">byte</span>[])array.clone());</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 默认是堆内分配</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ByteBuf <span class="title">wrappedBuffer</span><span class="params">(<span class="keyword">byte</span>[] array)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">return</span> (ByteBuf)(array.length == <span class="number">0</span>?EMPTY_BUFFER:<span class="keyword">new</span> UnpooledHeapByteBuf(ALLOC, array, array.length));</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// 也提供了堆外分配的方法</span></span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ByteBufAllocator ALLOC;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ByteBuf <span class="title">directBuffer</span><span class="params">(<span class="keyword">int</span> initialCapacity)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">return</span> ALLOC.directBuffer(initialCapacity);</span><br><span class="line">   &#125;	</span><br></pre></td></tr></table></figure>
<p>相对底层的分配方法是使用ByteBufAllocator，Netty实现了PooledByteBufAllocator和UnpooledByteBufAllocator，前者使用了<a target="_blank" rel="noopener" href="https://github.com/jemalloc/jemalloc">jemalloc（一种malloc()的实现）</a>来分配内存，并且实现了对ByteBuf的池化以提高性能。后者分配的是未池化的ByteBuf，其分配方式与之前讲的一致。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Channel channel = ...;</span><br><span class="line">ByteBufAllocator allocator = channel.alloc();</span><br><span class="line">ByteBuf buffer = allocator.directBuffer();</span><br><span class="line"><span class="keyword">do</span> something.......</span><br></pre></td></tr></table></figure>
<p>为了优化内存使用率，Netty提供了一套手动的方式来追踪不活跃对象，像UnpooledHeapByteBuf这种分配在堆内的对象得益于JVM的GC管理，无需额外操心，而UnpooledDirectByteBuf是在堆外分配的，它的内部基于DirectByteBuffer，DirectByteBuffer会先向Bits类申请一个额度（Bits还拥有一个全局变量totalCapacity，记录了所有DirectByteBuffer总大小），每次申请前都会查看是否已经超过-XX:MaxDirectMemorySize所设置的上限，如果超限就会尝试调用Sytem.gc()，以试图回收一部分内存，然后休眠100毫秒，如果内存还是不足，则只能抛出OOM异常。堆外内存的回收虽然有了这么一层保障，但为了提高性能与使用率，主动回收也是很有必要的。由于Netty还实现了ByteBuf的池化，像PooledHeapByteBuf和PooledDirectByteBuf就必须依赖于手动的方式来进行回收（放回池中）。</p>
<p>Netty使用了引用计数器的方式来追踪那些不活跃的对象。引用计数的接口为ReferenceCounted，它的思想很简单，只要ByteBuf对象的引用计数大于0，就保证该对象不会被释放回收，可以通过手动调用release()与retain()方法来操作该对象的引用计数值递减或递增。用户也可以通过自定义一个ReferenceCounted的实现类，以满足自定义的规则。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> io.netty.buffer;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">AbstractReferenceCountedByteBuf</span> <span class="keyword">extends</span> <span class="title">AbstractByteBuf</span> </span>&#123;</span><br><span class="line">	<span class="comment">// 由于ByteBuf的实例对象会非常多,所以这里没有将refCnt包装为AtomicInteger</span></span><br><span class="line">	<span class="comment">// 而是使用一个全局的AtomicIntegerFieldUpdater来负责操作refCnt</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> AtomicIntegerFieldUpdater&lt;AbstractReferenceCountedByteBuf&gt; refCntUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, <span class="string">&quot;refCnt&quot;</span>);</span><br><span class="line">	<span class="comment">// 每个ByteBuf的初始引用值都为1</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> refCnt = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">refCnt</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.refCnt;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">setRefCnt</span><span class="params">(<span class="keyword">int</span> refCnt)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.refCnt = refCnt;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">    <span class="function"><span class="keyword">public</span> ByteBuf <span class="title">retain</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.retain0(<span class="number">1</span>);</span><br><span class="line">    &#125;	</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 引用计数值递增increment，increment必须大于0</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ByteBuf <span class="title">retain</span><span class="params">(<span class="keyword">int</span> increment)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.retain0(ObjectUtil.checkPositive(increment, <span class="string">&quot;increment&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">checkPositive</span><span class="params">(<span class="keyword">int</span> i, String name)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(i &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(name + <span class="string">&quot;: &quot;</span> + i + <span class="string">&quot; (expected: &gt; 0)&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> i;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 使用CAS操作不断尝试更新值</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> ByteBuf <span class="title">retain0</span><span class="params">(<span class="keyword">int</span> increment)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> refCnt;</span><br><span class="line">        <span class="keyword">int</span> nextCnt;</span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            refCnt = <span class="keyword">this</span>.refCnt;</span><br><span class="line">            nextCnt = refCnt + increment;</span><br><span class="line">            <span class="keyword">if</span>(nextCnt &lt;= increment) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> IllegalReferenceCountException(refCnt, increment);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">while</span>(!refCntUpdater.compareAndSet(<span class="keyword">this</span>, refCnt, nextCnt));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">release</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.release0(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">release</span><span class="params">(<span class="keyword">int</span> decrement)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.release0(ObjectUtil.checkPositive(decrement, <span class="string">&quot;decrement&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">release0</span><span class="params">(<span class="keyword">int</span> decrement)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> refCnt;</span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            refCnt = <span class="keyword">this</span>.refCnt;</span><br><span class="line">            <span class="keyword">if</span>(refCnt &lt; decrement) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> IllegalReferenceCountException(refCnt, -decrement);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">while</span>(!refCntUpdater.compareAndSet(<span class="keyword">this</span>, refCnt, refCnt - decrement));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(refCnt == decrement) &#123;</span><br><span class="line">            <span class="keyword">this</span>.deallocate(); </span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">deallocate</span><span class="params">()</span></span>;	</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="Channel"><a href="#Channel" class="headerlink" title="Channel"></a>Channel</h3><hr>
<p>Netty中的Channel与Java NIO的概念一样，都是对一个实体或连接的抽象，但Netty提供了一套更加通用的API。就以网络套接字为例，在Java中OIO与NIO是截然不同的两套API，假设你之前使用的是OIO而又想更改为NIO实现，那么几乎需要重写所有代码。而在Netty中，只需要更改短短几行代码（更改Channel与EventLoop的实现类，如把OioServerSocketChannel替换为NioServerSocketChannel），就可以完成OIO与NIO（或其他）之间的转换。</p>
<p><img src="http://wx2.sinaimg.cn/large/63503acbly1fm103i127ej20xe0f074y.jpg"></p>
<p>每个Channel最终都会被分配一个ChannelPipeline和ChannelConfig，前者持有所有负责处理入站与出站数据以及事件的ChannelHandler，后者包含了该Channel的所有配置设置，并且支持热更新，由于不同的传输类型可能具有其特别的配置，所以该类可能会实现为ChannelConfig的不同子类。</p>
<p>Channel是线程安全的（与之后要讲的线程模型有关），因此你完全可以在多个线程中复用同一个Channel，就像如下代码所示。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> Channel channel = ...</span><br><span class="line"><span class="keyword">final</span> ByteBuf buffer = Unpooled.copiedBuffer(<span class="string">&quot;Hello,World!&quot;</span>, CharsetUtil.UTF_8).retain();</span><br><span class="line">Runnable writer = <span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">		channel.writeAndFlush(buffer.duplicate());</span><br><span class="line">	&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">Executor executor = Executors.newCachedThreadPool();</span><br><span class="line">executor.execute(writer);</span><br><span class="line">executor.execute(writer);</span><br><span class="line">.......</span><br></pre></td></tr></table></figure>
<p>Netty除了支持常见的NIO与OIO，还内置了其他的传输类型。</p>
<table>
<thead>
<tr>
<th>Nmae</th>
<th>Package</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td>NIO</td>
<td>io.netty.channel.socket.nio</td>
<td>以Java NIO为基础实现</td>
</tr>
<tr>
<td>OIO</td>
<td>io.netty.channel.socket.oio</td>
<td>以java.net为基础实现，使用阻塞I/O模型</td>
</tr>
<tr>
<td>Epoll</td>
<td>io.netty.channel.epoll</td>
<td>由JNI驱动epoll()实现的更高性能的非阻塞I/O，它只能使用在Linux</td>
</tr>
<tr>
<td>Local</td>
<td>io.netty.channel.local</td>
<td>本地传输，在JVM内部通过管道进行通信</td>
</tr>
<tr>
<td>Embedded</td>
<td>io.netty.channel.embedded</td>
<td>允许在不需要真实网络传输的环境下使用ChannelHandler，主要用于对ChannelHandler进行测试</td>
</tr>
</tbody></table>
<p>NIO、OIO、Epoll我们应该已经很熟悉了，下面主要说说Local与Embedded。</p>
<p>Local传输用于在同一个JVM中运行的客户端和服务器程序之间的异步通信，与服务器Channel相关联的SocketAddress并没有绑定真正的物理网络地址，它会被存储在注册表中，并在Channel关闭时注销。因此Local传输不会接受真正的网络流量，也就是说它不能与其他传输实现进行互操作。</p>
<p>Embedded传输主要用于对ChannelHandler进行单元测试，ChannelHandler是用于处理消息的逻辑组件，Netty通过将入站消息与出站消息都写入到EmbeddedChannel中的方式（提供了write/readInbound()与write/readOutbound()来读写入站与出站消息）来实现对ChannelHandler的单元测试。</p>
<h3 id="ChannelHandler"><a href="#ChannelHandler" class="headerlink" title="ChannelHandler"></a>ChannelHandler</h3><hr>
<p>ChannelHandler充当了处理入站和出站数据的应用程序逻辑的容器，该类是基于事件驱动的，它会响应相关的事件然后去调用其关联的回调函数，例如当一个新的连接被建立时，ChannelHandler的channelActive()方法将会被调用。</p>
<p>关于入站消息和出站消息的数据流向定义，如果以客户端为主视角来说的话，那么从客户端流向服务器的数据被称为出站，反之为入站。</p>
<p>入站事件是可能被入站数据或者相关的状态更改而触发的事件，包括：连接已被激活、连接失活、读取入站数据、用户事件、发生异常等。</p>
<p>出站事件是未来将会触发的某个动作的结果的事件，这些动作包括：打开或关闭远程节点的连接、将数据写（或冲刷）到套接字。</p>
<p>ChannelHandler的主要用途包括：</p>
<ul>
<li><p>对入站与出站数据的业务逻辑处理</p>
</li>
<li><p>记录日志</p>
</li>
<li><p>将数据从一种格式转换为另一种格式，实现编解码器。以一次HTTP协议（或者其他应用层协议）的流程为例，数据在网络传输时的单位为字节，当客户端发送请求到服务器时，服务器需要通过解码器（处理入站消息）将字节解码为协议的消息内容，服务器在发送响应的时候（处理出站消息），还需要通过编码器将消息内容编码为字节。</p>
</li>
<li><p>捕获异常</p>
</li>
<li><p>提供Channel生命周期内的通知，如Channel活动时与非活动时</p>
</li>
</ul>
<p>Netty中到处都充满了异步与事件驱动，而回调函数正是用于响应事件之后的操作。由于异步会直接返回一个结果，所以Netty提供了ChannelFuture（实现了java.util.concurrent.Future）来作为异步调用返回的占位符，真正的结果会在未来的某个时刻完成，到时候就可以通过ChannelFuture对其进行访问，每个Netty的出站I/O操作都将会返回一个ChannelFuture。</p>
<p>Netty还提供了ChannelFutureListener接口来监听ChannelFuture是否成功，并采取对应的操作。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Channel channel = ...</span><br><span class="line">ChannelFuture future = channel.connect(<span class="keyword">new</span> InetSocketAddress(<span class="string">&quot;192.168.0.1&quot;</span>,<span class="number">6666</span>));</span><br><span class="line"><span class="comment">// 注册一个监听器</span></span><br><span class="line">future.addListener(<span class="keyword">new</span> ChannelFutureListener() &#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">operationComplete</span><span class="params">(ChannelFuture future)</span> </span>&#123;</span><br><span class="line">		<span class="keyword">if</span> (future.isSuccess()) &#123;</span><br><span class="line">			<span class="comment">// do something....</span></span><br><span class="line">		&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">			<span class="comment">// 输出错误信息</span></span><br><span class="line">			Throwable cause = future.cause();</span><br><span class="line">			cause.printStackTrace();</span><br><span class="line">			<span class="comment">// do something....</span></span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>ChannelFutureListener接口中还提供了几个简单的默认实现，方便我们使用。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> io.netty.channel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelFuture;</span><br><span class="line"><span class="keyword">import</span> io.netty.util.concurrent.GenericFutureListener;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ChannelFutureListener</span> <span class="keyword">extends</span> <span class="title">GenericFutureListener</span>&lt;<span class="title">ChannelFuture</span>&gt; </span>&#123;</span><br><span class="line">	<span class="comment">// 在Future完成时关闭</span></span><br><span class="line">    ChannelFutureListener CLOSE = <span class="keyword">new</span> ChannelFutureListener() &#123;</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">operationComplete</span><span class="params">(ChannelFuture future)</span> </span>&#123;</span><br><span class="line">            future.channel().close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">	<span class="comment">// 如果失败则关闭</span></span><br><span class="line">    ChannelFutureListener CLOSE_ON_FAILURE = <span class="keyword">new</span> ChannelFutureListener() &#123;</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">operationComplete</span><span class="params">(ChannelFuture future)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">if</span>(!future.isSuccess()) &#123;</span><br><span class="line">                future.channel().close();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">	<span class="comment">// 将异常信息传递给下一个ChannelHandler</span></span><br><span class="line">    ChannelFutureListener FIRE_EXCEPTION_ON_FAILURE = <span class="keyword">new</span> ChannelFutureListener() &#123;</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">operationComplete</span><span class="params">(ChannelFuture future)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">if</span>(!future.isSuccess()) &#123;</span><br><span class="line">                future.channel().pipeline().fireExceptionCaught(future.cause());</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>ChannelHandler接口定义了对它生命周期进行监听的回调函数，在ChannelHandler被添加到ChannelPipeline或者被移除时都会调用这些函数。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> io.netty.channel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ChannelHandler</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handlerAdded</span><span class="params">(ChannelHandlerContext var1)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">handlerRemoved</span><span class="params">(ChannelHandlerContext var1)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** <span class="doctag">@deprecated</span> */</span></span><br><span class="line">    <span class="meta">@Deprecated</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exceptionCaught</span><span class="params">(ChannelHandlerContext var1, Throwable var2)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 该注解表明这个ChannelHandler可被其他线程复用</span></span><br><span class="line">    <span class="meta">@Inherited</span></span><br><span class="line">    <span class="meta">@Documented</span></span><br><span class="line">    <span class="meta">@Target(&#123;ElementType.TYPE&#125;)</span></span><br><span class="line">    <span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="meta">@interface</span> Sharable &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>入站消息与出站消息由其对应的接口ChannelInboundHandler与ChannelOutboundHandler负责，这两个接口定义了监听Channel的生命周期的状态改变事件的回调函数。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> io.netty.channel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelHandler;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelHandlerContext;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ChannelInboundHandler</span> <span class="keyword">extends</span> <span class="title">ChannelHandler</span> </span>&#123;</span><br><span class="line">	<span class="comment">// 当channel被注册到EventLoop时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">channelRegistered</span><span class="params">(ChannelHandlerContext var1)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 当channel已经被创建，但还未注册到EventLoop（或者从EventLoop中注销）被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">channelUnregistered</span><span class="params">(ChannelHandlerContext var1)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 当channel处于活动状态（连接到远程节点）被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">channelActive</span><span class="params">(ChannelHandlerContext var1)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 当channel处于非活动状态（没有连接到远程节点）被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">channelInactive</span><span class="params">(ChannelHandlerContext var1)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 当从channel读取数据时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">channelRead</span><span class="params">(ChannelHandlerContext var1, Object var2)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 当channel的上一个读操作完成时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">channelReadComplete</span><span class="params">(ChannelHandlerContext var1)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 当ChannelInboundHandler.fireUserEventTriggered()方法被调用时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">userEventTriggered</span><span class="params">(ChannelHandlerContext var1, Object var2)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 当channel的可写状态发生改变时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">channelWritabilityChanged</span><span class="params">(ChannelHandlerContext var1)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 当处理过程中发生异常时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exceptionCaught</span><span class="params">(ChannelHandlerContext var1, Throwable var2)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> io.netty.channel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelHandler;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelHandlerContext;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelPromise;</span><br><span class="line"><span class="keyword">import</span> java.net.SocketAddress;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ChannelOutboundHandler</span> <span class="keyword">extends</span> <span class="title">ChannelHandler</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 当请求将Channel绑定到一个地址时被调用</span></span><br><span class="line">	<span class="comment">// ChannelPromise是ChannelFuture的一个子接口，定义了如setSuccess(),setFailure()等方法</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">bind</span><span class="params">(ChannelHandlerContext var1, SocketAddress var2, ChannelPromise var3)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 当请求将Channel连接到远程节点时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">connect</span><span class="params">(ChannelHandlerContext var1, SocketAddress var2, SocketAddress var3, ChannelPromise var4)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 当请求将Channel从远程节点断开时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">disconnect</span><span class="params">(ChannelHandlerContext var1, ChannelPromise var2)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 当请求关闭Channel时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">close</span><span class="params">(ChannelHandlerContext var1, ChannelPromise var2)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 当请求将Channel从它的EventLoop中注销时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">deregister</span><span class="params">(ChannelHandlerContext var1, ChannelPromise var2)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 当请求从Channel读取数据时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">read</span><span class="params">(ChannelHandlerContext var1)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 当请求通过Channel将数据写到远程节点时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">write</span><span class="params">(ChannelHandlerContext var1, Object var2, ChannelPromise var3)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 当请求通过Channel将缓冲中的数据冲刷到远程节点时被调用</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">flush</span><span class="params">(ChannelHandlerContext var1)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>通过实现ChannelInboundHandler或者ChannelOutboundHandler就可以完成用户自定义的应用逻辑处理程序，不过Netty已经帮你实现了一些基本操作，用户只需要继承并扩展ChannelInboundHandlerAdapter或ChannelOutboundHandlerAdapter来作为自定义实现的起始点。</p>
<p>ChannelInboundHandlerAdapter与ChannelOutboundHandlerAdapter都继承于ChannelHandlerAdapter，该抽象类简单实现了ChannelHandler接口。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">ChannelHandlerAdapter</span> <span class="keyword">implements</span> <span class="title">ChannelHandler</span> </span>&#123;</span><br><span class="line">    <span class="keyword">boolean</span> added;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">ChannelHandlerAdapter</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 该方法不允许将此ChannelHandler共享复用</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">ensureNotSharable</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.isSharable()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">&quot;ChannelHandler &quot;</span> + <span class="keyword">this</span>.getClass().getName() + <span class="string">&quot; is not allowed to be shared&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 使用反射判断实现类有没有@Sharable注解，以确认该类是否为可共享复用的</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isSharable</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Class clazz = <span class="keyword">this</span>.getClass();</span><br><span class="line">        Map cache = InternalThreadLocalMap.get().handlerSharableCache();</span><br><span class="line">        Boolean sharable = (Boolean)cache.get(clazz);</span><br><span class="line">        <span class="keyword">if</span>(sharable == <span class="keyword">null</span>) &#123;</span><br><span class="line">            sharable = Boolean.valueOf(clazz.isAnnotationPresent(Sharable.class));</span><br><span class="line">            cache.put(clazz, sharable);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> sharable.booleanValue();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handlerAdded</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handlerRemoved</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">exceptionCaught</span><span class="params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.fireExceptionCaught(cause);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>ChannelInboundHandlerAdapter与ChannelOutboundHandlerAdapter默认只是简单地将请求传递给ChannelPipeline中的下一个ChannelHandler，源码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ChannelInboundHandlerAdapter</span> <span class="keyword">extends</span> <span class="title">ChannelHandlerAdapter</span> <span class="keyword">implements</span> <span class="title">ChannelInboundHandler</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">ChannelInboundHandlerAdapter</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelRegistered</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.fireChannelRegistered();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelUnregistered</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.fireChannelUnregistered();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.fireChannelActive();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelInactive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.fireChannelInactive();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.fireChannelRead(msg);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelReadComplete</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.fireChannelReadComplete();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">userEventTriggered</span><span class="params">(ChannelHandlerContext ctx, Object evt)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.fireUserEventTriggered(evt);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelWritabilityChanged</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.fireChannelWritabilityChanged();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">exceptionCaught</span><span class="params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.fireExceptionCaught(cause);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ChannelOutboundHandlerAdapter</span> <span class="keyword">extends</span> <span class="title">ChannelHandlerAdapter</span> <span class="keyword">implements</span> <span class="title">ChannelOutboundHandler</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">ChannelOutboundHandlerAdapter</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">bind</span><span class="params">(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.bind(localAddress, promise);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">connect</span><span class="params">(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.connect(remoteAddress, localAddress, promise);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">disconnect</span><span class="params">(ChannelHandlerContext ctx, ChannelPromise promise)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.disconnect(promise);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">close</span><span class="params">(ChannelHandlerContext ctx, ChannelPromise promise)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.close(promise);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deregister</span><span class="params">(ChannelHandlerContext ctx, ChannelPromise promise)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.deregister(promise);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">read</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.read();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">write</span><span class="params">(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.write(msg, promise);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">flush</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.flush();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>对于处理入站消息，另外一种选择是继承SimpleChannelInboundHandler，它是Netty的一个继承于ChannelInboundHandlerAdapter的抽象类，并在其之上实现了自动释放资源的功能。</p>
<p>我们在了解ByteBuf时就已经知道了Netty使用了一套自己实现的引用计数算法来主动释放资源，假设你的ChannelHandler继承于ChannelInboundHandlerAdapter或ChannelOutboundHandlerAdapter，那么你就有责任去管理你所分配的ByteBuf，一般来说，一个消息对象（ByteBuf）已经被消费（或丢弃）了，并且不会传递给ChannelHandler链中的下一个处理器（如果该消息到达了实际的传输层，那么当它被写入或Channel关闭时，都会被自动释放），那么你就需要去手动释放它。通过一个简单的工具类ReferenceCountUtil的release方法，就可以做到这一点。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这个泛型为消息对象的类型</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleChannelInboundHandler</span>&lt;<span class="title">I</span>&gt; <span class="keyword">extends</span> <span class="title">ChannelInboundHandlerAdapter</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> TypeParameterMatcher matcher;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">boolean</span> autoRelease;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">SimpleChannelInboundHandler</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(<span class="keyword">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">SimpleChannelInboundHandler</span><span class="params">(<span class="keyword">boolean</span> autoRelease)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.matcher = TypeParameterMatcher.find(<span class="keyword">this</span>, SimpleChannelInboundHandler.class, <span class="string">&quot;I&quot;</span>);</span><br><span class="line">        <span class="keyword">this</span>.autoRelease = autoRelease;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">SimpleChannelInboundHandler</span><span class="params">(Class&lt;? extends I&gt; inboundMessageType)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(inboundMessageType, <span class="keyword">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">SimpleChannelInboundHandler</span><span class="params">(Class&lt;? extends I&gt; inboundMessageType, <span class="keyword">boolean</span> autoRelease)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.matcher = TypeParameterMatcher.get(inboundMessageType);</span><br><span class="line">        <span class="keyword">this</span>.autoRelease = autoRelease;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">acceptInboundMessage</span><span class="params">(Object msg)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.matcher.match(msg);</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// SimpleChannelInboundHandler只是替你做了ReferenceCountUtil.release()</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">boolean</span> release = <span class="keyword">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">if</span>(<span class="keyword">this</span>.acceptInboundMessage(msg)) &#123;</span><br><span class="line">                <span class="keyword">this</span>.channelRead0(ctx, msg);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                release = <span class="keyword">false</span>;</span><br><span class="line">                ctx.fireChannelRead(msg);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">if</span>(<span class="keyword">this</span>.autoRelease &amp;&amp; release) &#123;</span><br><span class="line">                ReferenceCountUtil.release(msg);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">// 这个方法才是我们需要实现的方法</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">channelRead0</span><span class="params">(ChannelHandlerContext var1, I var2)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">&#125;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// ReferenceCountUtil中的源码，release方法对消息对象的类型进行判断然后调用它的release()方法</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">release</span><span class="params">(Object msg)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> msg <span class="keyword">instanceof</span> ReferenceCounted?((ReferenceCounted)msg).release():<span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
<h3 id="ChannelPipeline"><a href="#ChannelPipeline" class="headerlink" title="ChannelPipeline"></a>ChannelPipeline</h3><hr>
<p>为了模块化与解耦合，不可能由一个ChannelHandler来完成所有应用逻辑，所以Netty采用了拦截器链的设计。ChannelPipeline就是用来管理ChannelHandler实例链的容器，它的职责就是保证实例链的流动。</p>
<p>每一个新创建的Channel都将会被分配一个新的ChannelPipeline，这种关联关系是永久性的，一个Channel一生只能对应一个ChannelPipeline。</p>
<p><img src="http://wx3.sinaimg.cn/large/63503acbly1fm1er9l4jfj213h0fcq3d.jpg"></p>
<p>一个入站事件被触发时，它会先从ChannelPipeline的最左端（头部）开始一直传播到ChannelPipeline的最右端（尾部），而出站事件正好与入站事件顺序相反（从最右端一直传播到最左端）。这个顺序是定死的，Netty总是将ChannelPipeline的入站口作为头部，而将出站口作为尾部。在事件传播的过程中，ChannelPipeline会判断下一个ChannelHandler的类型是否和事件的运动方向相匹配，如果不匹配，就跳过该ChannelHandler并继续检查下一个（保证入站事件只会被ChannelInboundHandler处理），一个ChannelHandler也可以同时实现ChannelInboundHandler与ChannelOutboundHandler，它在入站事件与出站事件中都会被调用。</p>
<p>在阅读ChannelHandler的源码时，发现很多方法需要一个ChannelHandlerContext类型的参数，该接口是ChannelPipeline与ChannelHandler之间相关联的关键。ChannelHandlerContext可以通知ChannelPipeline中的当前ChannelHandler的下一个ChannelHandler，还可以动态地改变当前ChannelHandler在ChannelPipeline中的位置（通过调用ChannelPipeline中的各种方法来修改）。</p>
<p>ChannelHandlerContext负责了在同一个ChannelPipeline中的ChannelHandler与其他ChannelHandler之间的交互，每个ChannelHandlerContext都对应了一个ChannelHandler。在DefaultChannelPipeline的源码中，已经表现的很明显了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DefaultChannelPipeline</span> <span class="keyword">implements</span> <span class="title">ChannelPipeline</span> </span>&#123;</span><br><span class="line">    .........</span><br><span class="line">	<span class="comment">// 头部节点和尾部节点的引用变量</span></span><br><span class="line">	<span class="comment">// ChannelHandlerContext在ChannelPipeline中是以链表的形式组织的</span></span><br><span class="line">    <span class="keyword">final</span> AbstractChannelHandlerContext head; </span><br><span class="line">    <span class="keyword">final</span> AbstractChannelHandlerContext tail;	</span><br><span class="line">	.........</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 添加一个ChannelHandler到链表尾部</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> ChannelPipeline <span class="title">addLast</span><span class="params">(String name, ChannelHandler handler)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.addLast((EventExecutorGroup)<span class="keyword">null</span>, name, handler);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> ChannelPipeline <span class="title">addLast</span><span class="params">(EventExecutorGroup group, String name, ChannelHandler handler)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> AbstractChannelHandlerContext newCtx;</span><br><span class="line">        <span class="keyword">synchronized</span>(<span class="keyword">this</span>) &#123;</span><br><span class="line">			<span class="comment">// 检查ChannelHandler是否为一个共享对象(@Sharable)</span></span><br><span class="line">			<span class="comment">// 如果该ChannelHandler没有@Sharable注解，并且是已被添加过的那么就抛出异常</span></span><br><span class="line">            checkMultiplicity(handler); </span><br><span class="line">			<span class="comment">// 返回一个DefaultChannelHandlerContext，注意该对象持有了传入的ChannelHandler</span></span><br><span class="line">            newCtx = <span class="keyword">this</span>.newContext(group, <span class="keyword">this</span>.filterName(name, handler), handler);</span><br><span class="line">            <span class="keyword">this</span>.addLast0(newCtx);</span><br><span class="line">			<span class="comment">// 如果当前ChannelPipeline没有被注册，那么就先加到未决链表中</span></span><br><span class="line">            <span class="keyword">if</span>(!<span class="keyword">this</span>.registered) &#123;</span><br><span class="line">                newCtx.setAddPending();</span><br><span class="line">                <span class="keyword">this</span>.callHandlerCallbackLater(newCtx, <span class="keyword">true</span>);</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">            &#125;</span><br><span class="line">			</span><br><span class="line">			<span class="comment">// 否则就调用ChannelHandler中的handlerAdded()</span></span><br><span class="line">            EventExecutor executor = newCtx.executor();</span><br><span class="line">            <span class="keyword">if</span>(!executor.inEventLoop()) &#123;</span><br><span class="line">                newCtx.setAddPending();</span><br><span class="line">                executor.execute(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                        DefaultChannelPipeline.<span class="keyword">this</span>.callHandlerAdded0(newCtx);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;);</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">this</span>.callHandlerAdded0(newCtx);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 将新的ChannelHandlerContext插入到尾部与尾部之前的节点之间</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">addLast0</span><span class="params">(AbstractChannelHandlerContext newCtx)</span> </span>&#123;</span><br><span class="line">        AbstractChannelHandlerContext prev = <span class="keyword">this</span>.tail.prev;</span><br><span class="line">        newCtx.prev = prev;</span><br><span class="line">        newCtx.next = <span class="keyword">this</span>.tail;</span><br><span class="line">        prev.next = newCtx;</span><br><span class="line">        <span class="keyword">this</span>.tail.prev = newCtx;</span><br><span class="line">    &#125;</span><br><span class="line">	.....</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>ChannelHandlerContext还定义了许多与Channel和ChannelPipeline重合的方法（像read()、write()、connect()这些用于出站的方法或者如fireChannelXXXX()这样用于入站的方法），不同之处在于调用Channel或者ChannelPipeline上的这些方法，它们将会从头沿着整个ChannelHandler实例链进行传播，而调用位于ChannelHandlerContext上的相同方法，则会从当前所关联的ChannelHandler开始，且只会传播给实例链中的下一个ChannelHandler。而且，事件之间的移动（从一个ChannelHandler到下一个ChannelHandler）也是通过ChannelHandlerContext中的方法调用完成的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DefaultChannelPipeline</span> <span class="keyword">implements</span> <span class="title">ChannelPipeline</span> </span>&#123;	</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> ChannelPipeline <span class="title">fireChannelRead</span><span class="params">(Object msg)</span> </span>&#123;</span><br><span class="line">		<span class="comment">// 注意这里将头节点传入了进去</span></span><br><span class="line">        AbstractChannelHandlerContext.invokeChannelRead(<span class="keyword">this</span>.head, msg);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">AbstractChannelHandlerContext</span> <span class="keyword">extends</span> <span class="title">DefaultAttributeMap</span> <span class="keyword">implements</span> <span class="title">ChannelHandlerContext</span>, <span class="title">ResourceLeakHint</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">invokeChannelRead</span><span class="params">(<span class="keyword">final</span> AbstractChannelHandlerContext next, Object msg)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, <span class="string">&quot;msg&quot;</span>), next);</span><br><span class="line">        EventExecutor executor = next.executor();</span><br><span class="line">        <span class="keyword">if</span>(executor.inEventLoop()) &#123;</span><br><span class="line">            next.invokeChannelRead(m);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            executor.execute(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    next.invokeChannelRead(m);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">invokeChannelRead</span><span class="params">(Object msg)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.invokeHandler()) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                ((ChannelInboundHandler)<span class="keyword">this</span>.handler()).channelRead(<span class="keyword">this</span>, msg);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Throwable var3) &#123;</span><br><span class="line">                <span class="keyword">this</span>.notifyHandlerException(var3);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">			<span class="comment">// 寻找下一个ChannelHandler</span></span><br><span class="line">            <span class="keyword">this</span>.fireChannelRead(msg);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;	</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ChannelHandlerContext <span class="title">fireChannelRead</span><span class="params">(Object msg)</span> </span>&#123;</span><br><span class="line">        invokeChannelRead(<span class="keyword">this</span>.findContextInbound(), msg);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> AbstractChannelHandlerContext <span class="title">findContextInbound</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        AbstractChannelHandlerContext ctx = <span class="keyword">this</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            ctx = ctx.next;</span><br><span class="line">        &#125; <span class="keyword">while</span>(!ctx.inbound); <span class="comment">// 直到找到一个ChannelInboundHandler</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> ctx;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="EventLoop"><a href="#EventLoop" class="headerlink" title="EventLoop"></a>EventLoop</h3><hr>
<p>为了最大限度地提供高性能和可维护性，Netty设计了一套强大又易用的线程模型。在一个网络框架中，最重要的能力是能够快速高效地处理在连接的生命周期内发生的各种事件，与之相匹配的程序构造被称为事件循环，Netty定义了接口EventLoop来负责这项工作。</p>
<p>如果是经常用Java进行多线程开发的童鞋想必经常会使用到线程池，也就是Executor这套API。Netty就是从Executor（java.util.concurrent）之上扩展了自己的EventExecutorGroup（io.netty.util.concurrent），同时为了与Channel的事件进行交互，还扩展了EventLoopGroup接口（io.netty.channel）。在io.netty.util.concurrent包下的EventExecutorXXX负责实现线程并发相关的工作，而在io.netty.channel包下的EventLoopXXX负责实现网络编程相关的工作（处理Channel中的事件）。</p>
<p><img src="http://wx3.sinaimg.cn/large/63503acbly1fm296hz0p9j20ff0kc3z2.jpg"></p>
<p>在Netty的线程模型中，一个EventLoop将由一个永远不会改变的Thread驱动，而一个Channel一生只会使用一个EventLoop（但是一个EventLoop可能会被指派用于服务多个Channel），在Channel中的所有I/O操作和事件都由EventLoop中的线程处理，也就是说一个Channel的一生之中都只会使用到一个线程。不过在Netty3，只有入站事件会被EventLoop处理，所有出站事件都会由调用线程处理，这种设计导致了ChannelHandler的线程安全问题。Netty4简化了线程模型，通过在同一个线程处理所有事件，既解决了这个问题，还提供了一个更加简单的架构。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> io.netty.channel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">SingleThreadEventLoop</span> <span class="keyword">extends</span> <span class="title">SingleThreadEventExecutor</span> <span class="keyword">implements</span> <span class="title">EventLoop</span> </span>&#123;</span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DEFAULT_MAX_PENDING_TASKS = Math.max(<span class="number">16</span>, SystemPropertyUtil.getInt(<span class="string">&quot;io.netty.eventLoop.maxPendingTasks&quot;</span>, <span class="number">2147483647</span>));</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Queue&lt;Runnable&gt; tailTasks;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">SingleThreadEventLoop</span><span class="params">(EventLoopGroup parent, ThreadFactory threadFactory, <span class="keyword">boolean</span> addTaskWakesUp)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(parent, threadFactory, addTaskWakesUp, DEFAULT_MAX_PENDING_TASKS, RejectedExecutionHandlers.reject());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">SingleThreadEventLoop</span><span class="params">(EventLoopGroup parent, Executor executor, <span class="keyword">boolean</span> addTaskWakesUp)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(parent, executor, addTaskWakesUp, DEFAULT_MAX_PENDING_TASKS, RejectedExecutionHandlers.reject());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">SingleThreadEventLoop</span><span class="params">(EventLoopGroup parent, ThreadFactory threadFactory, <span class="keyword">boolean</span> addTaskWakesUp, <span class="keyword">int</span> maxPendingTasks, RejectedExecutionHandler rejectedExecutionHandler)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>(parent, threadFactory, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);</span><br><span class="line">        <span class="keyword">this</span>.tailTasks = <span class="keyword">this</span>.newTaskQueue(maxPendingTasks);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">SingleThreadEventLoop</span><span class="params">(EventLoopGroup parent, Executor executor, <span class="keyword">boolean</span> addTaskWakesUp, <span class="keyword">int</span> maxPendingTasks, RejectedExecutionHandler rejectedExecutionHandler)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);</span><br><span class="line">        <span class="keyword">this</span>.tailTasks = <span class="keyword">this</span>.newTaskQueue(maxPendingTasks);</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 返回它所在的EventLoopGroup</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> EventLoopGroup <span class="title">parent</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> (EventLoopGroup)<span class="keyword">super</span>.parent();</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">    <span class="function"><span class="keyword">public</span> EventLoop <span class="title">next</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> (EventLoop)<span class="keyword">super</span>.next();</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 注册Channel,这里ChannelPromise和Channel关联到了一起</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ChannelFuture <span class="title">register</span><span class="params">(Channel channel)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.register((ChannelPromise)(<span class="keyword">new</span> DefaultChannelPromise(channel, <span class="keyword">this</span>)));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ChannelFuture <span class="title">register</span><span class="params">(ChannelPromise promise)</span> </span>&#123;</span><br><span class="line">        ObjectUtil.checkNotNull(promise, <span class="string">&quot;promise&quot;</span>);</span><br><span class="line">        promise.channel().unsafe().register(<span class="keyword">this</span>, promise);</span><br><span class="line">        <span class="keyword">return</span> promise;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 剩下这些函数都是用于调度任务</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">executeAfterEventLoopIteration</span><span class="params">(Runnable task)</span> </span>&#123;</span><br><span class="line">        ObjectUtil.checkNotNull(task, <span class="string">&quot;task&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.isShutdown()) &#123;</span><br><span class="line">            reject();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(!<span class="keyword">this</span>.tailTasks.offer(task)) &#123;</span><br><span class="line">            <span class="keyword">this</span>.reject(task);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.wakesUpForTask(task)) &#123;</span><br><span class="line">            <span class="keyword">this</span>.wakeup(<span class="keyword">this</span>.inEventLoop());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">removeAfterEventLoopIterationTask</span><span class="params">(Runnable task)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.tailTasks.remove(ObjectUtil.checkNotNull(task, <span class="string">&quot;task&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">wakesUpForTask</span><span class="params">(Runnable task)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> !(task <span class="keyword">instanceof</span> SingleThreadEventLoop.NonWakeupRunnable);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">afterRunningAllTasks</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.runAllTasksFrom(<span class="keyword">this</span>.tailTasks);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">hasTasks</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">super</span>.hasTasks() || !<span class="keyword">this</span>.tailTasks.isEmpty();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">pendingTasks</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">super</span>.pendingTasks() + <span class="keyword">this</span>.tailTasks.size();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">interface</span> <span class="title">NonWakeupRunnable</span> <span class="keyword">extends</span> <span class="title">Runnable</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>为了确保一个Channel的整个生命周期中的I/O事件会被一个EventLoop负责，Netty通过inEventLoop()方法来判断当前执行的线程的身份，确定它是否是分配给当前Channel以及它的EventLoop的那一个线程。如果当前（调用）线程正是EventLoop中的线程，那么所提交的任务将会被直接执行，否则，EventLoop将调度该任务以便稍后执行，并将它放入内部的任务队列（每个EventLoop都有它自己的任务队列，从SingleThreadEventLoop的源码就能发现很多用于调度内部任务队列的方法），在下次处理它的事件时，将会执行队列中的那些任务。这种设计可以让任何线程与Channel直接交互，而无需在ChannelHandler中进行额外的同步。</p>
<p>从性能上来考虑，千万不要将一个需要长时间来运行的任务放入到任务队列中，它会影响到该队列中的其他任务的执行。解决方案是使用一个专门的EventExecutor来执行它（ChannelPipeline提供了带有EventExecutorGroup参数的addXXX()方法，该方法可以将传入的ChannelHandler绑定到你传入的EventExecutor之中），这样它就会在另一条线程中执行，与其他任务隔离。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">SingleThreadEventExecutor</span> <span class="keyword">extends</span> <span class="title">AbstractScheduledEventExecutor</span> <span class="keyword">implements</span> <span class="title">OrderedEventExecutor</span> </span>&#123;</span><br><span class="line">.....</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(Runnable task)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(task == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;task&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">boolean</span> inEventLoop = <span class="keyword">this</span>.inEventLoop();</span><br><span class="line">            <span class="keyword">if</span>(inEventLoop) &#123;</span><br><span class="line">                <span class="keyword">this</span>.addTask(task);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">this</span>.startThread();</span><br><span class="line">                <span class="keyword">this</span>.addTask(task);</span><br><span class="line">                <span class="keyword">if</span>(<span class="keyword">this</span>.isShutdown() &amp;&amp; <span class="keyword">this</span>.removeTask(task)) &#123;</span><br><span class="line">                    reject();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(!<span class="keyword">this</span>.addTaskWakesUp &amp;&amp; <span class="keyword">this</span>.wakesUpForTask(task)) &#123;</span><br><span class="line">                <span class="keyword">this</span>.wakeup(inEventLoop);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">inEventLoop</span><span class="params">(Thread thread)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> thread == <span class="keyword">this</span>.thread;</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">.....</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>EventLoopGroup负责管理和分配EventLoop（创建EventLoop和为每个新创建的Channel分配EventLoop），根据不同的传输类型，EventLoop的创建和分配方式也不同。例如，使用NIO传输类型，EventLoopGroup就会只使用较少的EventLoop（一个EventLoop服务于多个Channel），这是因为NIO基于I/O多路复用，一个线程可以处理多个连接，而如果使用的是OIO，那么新创建一个Channel（连接）就需要分配一个EventLoop（线程）。</p>
<h3 id="Bootstrap"><a href="#Bootstrap" class="headerlink" title="Bootstrap"></a>Bootstrap</h3><hr>
<p>在深入了解地Netty的核心组件之后，发现它们的设计都很模块化，如果想要实现你自己的应用程序，就需要将这些组件组装到一起。Netty通过Bootstrap类，以对一个Netty应用程序进行配置（组装各个组件），并最终使它运行起来。对于客户端程序和服务器程序所使用到的Bootstrap类是不同的，后者需要使用ServerBootstrap，这样设计是因为，在如TCP这样有连接的协议中，服务器程序往往需要一个以上的Channel，通过父Channel来接受来自客户端的连接，然后创建子Channel用于它们之间的通信，而像UDP这样无连接的协议，它不需要每个连接都创建子Channel，只需要一个Channel即可。</p>
<p>一个比较明显的差异就是Bootstrap与ServerBootstrap的group()方法，后者提供了一个接收2个EventLoopGroup的版本。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 该方法在Bootstrap的父类AbstractBootstrap中，泛型B为它当前子类的类型（为了链式调用）</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> B <span class="title">group</span><span class="params">(EventLoopGroup group)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">if</span>(group == <span class="keyword">null</span>) &#123;</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;group&quot;</span>);</span><br><span class="line">       &#125; <span class="keyword">else</span> <span class="keyword">if</span>(<span class="keyword">this</span>.group != <span class="keyword">null</span>) &#123;</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">&quot;group set already&quot;</span>);</span><br><span class="line">       &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">           <span class="keyword">this</span>.group = group;</span><br><span class="line">           <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ServerBootstrap中的实现，它也支持只用一个EventLoopGroup</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> ServerBootstrap <span class="title">group</span><span class="params">(EventLoopGroup group)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">this</span>.group(group, group);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> ServerBootstrap <span class="title">group</span><span class="params">(EventLoopGroup parentGroup, EventLoopGroup childGroup)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">super</span>.group(parentGroup);</span><br><span class="line">       <span class="keyword">if</span>(childGroup == <span class="keyword">null</span>) &#123;</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;childGroup&quot;</span>);</span><br><span class="line">       &#125; <span class="keyword">else</span> <span class="keyword">if</span>(<span class="keyword">this</span>.childGroup != <span class="keyword">null</span>) &#123;</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">&quot;childGroup set already&quot;</span>);</span><br><span class="line">       &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">           <span class="keyword">this</span>.childGroup = childGroup;</span><br><span class="line">           <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;	</span><br></pre></td></tr></table></figure>
<p>Bootstrap其实没有什么可以好说的，它就只是一个装配工，将各个组件拼装组合到一起，然后进行一些配置，有关它的详细API请参考<a target="_blank" rel="noopener" href="http://netty.io/4.1/api/index.html">Netty JavaDoc</a>。下面我们将通过一个经典的Echo客户端与服务器的例子，来梳理一遍创建Netty应用的流程。</p>
<p>首先实现的是服务器，我们先实现一个EchoServerInboundHandler，处理入站消息。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EchoServerInboundHandler</span> <span class="keyword">extends</span> <span class="title">ChannelInboundHandlerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ByteBuf in = (ByteBuf) msg;</span><br><span class="line">        System.out.printf(<span class="string">&quot;Server received: %s \n&quot;</span>, in.toString(CharsetUtil.UTF_8));</span><br><span class="line">		<span class="comment">// 由于读事件不是一次性就能把完整消息发送过来的，这里并没有调用writeAndFlush</span></span><br><span class="line">        ctx.write(in); <span class="comment">// 直接把消息写回给客户端(会被出站消息处理器处理,不过我们的应用没有实现任何出站消息处理器)</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelReadComplete</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="comment">// 等读事件已经完成时,冲刷之前写数据的缓冲区</span></span><br><span class="line">		<span class="comment">// 然后添加了一个监听器，它会在Future完成时进行关闭该Channel.</span></span><br><span class="line">        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)</span><br><span class="line">                .addListener(ChannelFutureListener.CLOSE);</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 处理异常，输出异常信息，然后关闭Channel</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">exceptionCaught</span><span class="params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        cause.printStackTrace();</span><br><span class="line">        ctx.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>服务器的应用逻辑只有这么多，剩下就是用ServerBootstrap进行配置了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EchoServer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> port;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">EchoServer</span><span class="params">(<span class="keyword">int</span> port)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.port = port;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> EchoServerInboundHandler serverHandler = <span class="keyword">new</span> EchoServerInboundHandler();</span><br><span class="line">        EventLoopGroup group = <span class="keyword">new</span> NioEventLoopGroup(); <span class="comment">// 传输类型使用NIO</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            ServerBootstrap b = <span class="keyword">new</span> ServerBootstrap();</span><br><span class="line">            b.group(group) <span class="comment">// 配置EventLoopGroup</span></span><br><span class="line">                    .channel(NioServerSocketChannel.class) <span class="comment">// 配置Channel的类型</span></span><br><span class="line">                    .localAddress(<span class="keyword">new</span> InetSocketAddress(port)) <span class="comment">// 配置端口号</span></span><br><span class="line">                    .childHandler(<span class="keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123; </span><br><span class="line">						<span class="comment">// 实现一个ChannelInitializer，它可以方便地添加多个ChannelHandler</span></span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">initChannel</span><span class="params">(SocketChannel socketChannel)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">                            socketChannel.pipeline().addLast(serverHandler);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">            <span class="comment">// i绑定地址，同步等待它完成</span></span><br><span class="line">            ChannelFuture f = b.bind().sync();</span><br><span class="line">            <span class="comment">// 关闭这个Future</span></span><br><span class="line">            f.channel().closeFuture().sync();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">			<span class="comment">// 关闭应用程序，一般来说Netty应用只需要调用这个方法就够了</span></span><br><span class="line">            group.shutdownGracefully().sync();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (args.length != <span class="number">1</span>) &#123;</span><br><span class="line">            System.err.printf(</span><br><span class="line">                    <span class="string">&quot;Usage: %s &lt;port&gt; \n&quot;</span>,</span><br><span class="line">                    EchoServer.class.getSimpleName()</span><br><span class="line">            );</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">int</span> port = Integer.parseInt(args[<span class="number">0</span>]);</span><br><span class="line">        <span class="keyword">new</span> EchoServer(port).start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>接下来实现客户端，同样需要先实现一个入站消息处理器。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EchoClientInboundHandler</span> <span class="keyword">extends</span> <span class="title">SimpleChannelInboundHandler</span>&lt;<span class="title">ByteBuf</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 我们在Channel连接到远程节点直接发送一条消息给服务器</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ctx.writeAndFlush(Unpooled.copiedBuffer(<span class="string">&quot;Hello, Netty!&quot;</span>, CharsetUtil.UTF_8));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">channelRead0</span><span class="params">(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">		<span class="comment">// 输出从服务器Echo的消息</span></span><br><span class="line">        System.out.printf(<span class="string">&quot;Client received: %s \n&quot;</span>, byteBuf.toString(CharsetUtil.UTF_8));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">exceptionCaught</span><span class="params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        cause.printStackTrace();</span><br><span class="line">        ctx.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然后配置客户端。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EchoClient</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String host;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> port;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">EchoClient</span><span class="params">(String host, <span class="keyword">int</span> port)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.host = host;</span><br><span class="line">        <span class="keyword">this</span>.port = port;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        EventLoopGroup group = <span class="keyword">new</span> NioEventLoopGroup();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Bootstrap b = <span class="keyword">new</span> Bootstrap();</span><br><span class="line">            b.group(group)</span><br><span class="line">                    .channel(NioSocketChannel.class)</span><br><span class="line">                    .remoteAddress(<span class="keyword">new</span> InetSocketAddress(host, port)) <span class="comment">// 服务器的地址</span></span><br><span class="line">                    .handler(<span class="keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123; </span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">initChannel</span><span class="params">(SocketChannel socketChannel)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">                            socketChannel.pipeline().addLast(<span class="keyword">new</span> EchoClientInboundHandler());</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">            ChannelFuture f = b.connect().sync(); <span class="comment">// 连接到服务器</span></span><br><span class="line">            f.channel().closeFuture().sync();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            group.shutdownGracefully().sync();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (args.length != <span class="number">2</span>) &#123;</span><br><span class="line">            System.err.printf(<span class="string">&quot;Usage: %s &lt;host&gt; &lt;port&gt; \n&quot;</span>, EchoClient.class.getSimpleName());</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        String host = args[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">int</span> port = Integer.parseInt(args[<span class="number">1</span>]);</span><br><span class="line">        <span class="keyword">new</span> EchoClient(host, port).start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>实现一个Netty应用程序就是如此简单，用户大多数都是在编写各种应用逻辑的ChannelHandler（或者使用Netty内置的各种实用ChannelHandler），然后只需要将它们全部添加到ChannelPipeline即可。</p>
<h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h3><hr>
<ul>
<li><p><a target="_blank" rel="noopener" href="https://netty.io/">Netty: Home</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://notes.shichao.io/unp/ch6/#io-multiplexing-model">Chapter 6. I/O Multiplexing: The select and poll Functions - Shichao’s Notes</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="http://man7.org/linux/man-pages/man7/epoll.7.html">epoll(7) - Linux manual page</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="http://tutorials.jenkov.com/java-nio/">Java NIO</a></p>
</li>
</ul>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh">
    <link itemprop="mainEntityOfPage" href="https://suyuhuan.gitee.io/yuwanzi.io/2017/11/19/2017-11-19-docker_introduction/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/yuwanzi.io/images/avatar.gif">
      <meta itemprop="name" content="玉丸子">
      <meta itemprop="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="玉丸子 | Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/yuwanzi.io/2017/11/19/2017-11-19-docker_introduction/" class="post-title-link" itemprop="url">Docker的那点事儿</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Veröffentlicht am</span>

      <time title="Erstellt: 2017-11-19 18:00:00" itemprop="dateCreated datePublished" datetime="2017-11-19T18:00:00+08:00">2017-11-19</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">Bearbeitet am</span>
        <time title="Geändert am: 2020-11-07 08:58:17" itemprop="dateModified" datetime="2020-11-07T08:58:17+08:00">2020-11-07</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">in</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%90%8E%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">后端</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%90%8E%E7%AB%AF/Docker/" itemprop="url" rel="index"><span itemprop="name">Docker</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h3 id="Docker是什么？"><a href="#Docker是什么？" class="headerlink" title="Docker是什么？"></a>Docker是什么？</h3><hr>
<p>Docker是一个基于轻量级虚拟化技术的容器，整个项目基于Go语言开发，并采用了Apache 2.0协议。Docker可以将我们的应用程序打包封装到一个容器中，该容器包含了应用程序的代码、运行环境、依赖库、配置文件等必需的资源，通过容器就可以实现方便快速并且与平台解耦的自动化部署方式，无论你部署时的环境如何，容器中的应用程序都会运行在同一种环境下。</p>
<p>举个栗子，小明写了一个CMS系统，该系统的技术栈非常广，需要依赖于各种开源库和中间件。如果按照纯手动的部署方式，小明需要安装各种开源软件，还需要写好每个开源软件的配置文件。如果只是部署一次，这点时间开销还是可以接受的，但如果小明每隔几天就需要换个服务器去部署他的程序，那么这些繁琐的重复工作无疑是会令人发狂的。这时候，Docker的用处就派上场了，小明只需要根据应用程序的部署步骤编写一份Dockerfile文件（将安装、配置等操作交由Docker自动化处理），然后构建并发布他的镜像，这样，不管在什么机器上，小明都只需要拉取他需要的镜像，然后就可以直接部署运行了，这正是Docker的魅力所在。</p>
<p>那么镜像又是什么呢？镜像是Docker中的一个重要概念：</p>
<ul>
<li><p>Image（镜像）：它类似于虚拟机中使用到的镜像，由于任何应用程序都需要有它自己的运行环境，Image就是用来提供所需运行环境的一个模板。</p>
</li>
<li><p>Container（容器）：Container是Docker提供的一个抽象层，它就像一个轻量级的沙盒，其中包含了一个极简的Linux系统环境与运行在其中的应用程序。Container是Image的运行实例（Image本身是只读的，Container启动时，Docker会在Image的上层创建一个可写层，任何在Container中的修改都不会影响到Image，如果想要在Image保存Container中的修改，Docker采用了基于Container生成新的Image层的策略），Docker引擎利用Container来操作并隔离每个应用（也就是说，每个容器中的应用都是互相独立的）。</p>
</li>
</ul>
<p>其实从Docker与Container的英文单词原意中就可以体会出Docker的思想。Container可以释义为集装箱，集装箱是一个可以便于机械设备装卸的封装货物的通用标准规格，它的发明简化了物流运输的机械化过程，使其建立起了一套标准化的物流运输体系。而Docker的意思为码头工人，可以认为，Docker就像是在码头上辛勤工作的工人，把应用打包成一个个具有某种标准化规格的”集装箱”（其实这里指出的集装箱对应的是Image，在Docker中Container更像是一个运行中的沙盒），当货物运输到目的地后，码头工人们（Docker）就可以把集装箱拆开取出其中的货物（基于Image来创建Container并运行）。这种标准化与隔离性可以很方便地组合使用多个Image来构建你的应用环境（Docker也提倡每个Image都遵循单一职责原则，也就是只做好一件事），或者与其他人共享你的Image。</p>
<blockquote>
<p>本文作者为<a target="_blank" rel="noopener" href="https://github.com/SylvanasSun">SylvanasSun(sylvanas.sun@gmail.com)</a>，首发于<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/">SylvanasSun’s Blog</a>。<br>原文链接：<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/2017/11/19/2017-11-19-docker_introduction/">https://sylvanassun.github.io/2017/11/19/2017-11-19-docker_introduction/</a><br>（转载请务必保留本段声明，并且保留超链接。）</p>
</blockquote>
<h3 id="Docker-VS-虚拟机"><a href="#Docker-VS-虚拟机" class="headerlink" title="Docker VS 虚拟机"></a>Docker VS 虚拟机</h3><hr>
<p>在上文中我们提到了Docker是基于轻量级虚拟化技术的，所以它与我们平常使用的虚拟机是不一样的。虚拟机技术可以分成以下两类：</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/0/08/Hardware_Virtualization_%28copy%29.svg" alt="系统虚拟机"></p>
<ul>
<li><p>系统虚拟机：通过软件对计算机系统的模拟来提供一个真实计算机的替代品。它是物理硬件的抽象并提供了运行完整操作系统所需的功能。虚拟机通过物理机器来管理和共享硬件，这样实现了多个虚拟机环境彼此之间的隔离，一台机器上可以运行多个虚拟机，每个虚拟机包括一个操作系统的完整副本。在系统虚拟机中，所运行的所有软件或操作都只会影响到该虚拟机的环境。我们经常使用的VMWare就是系统虚拟机的实现。</p>
</li>
<li><p>程序虚拟机：允许程序独立运行在平台之外。比较典型的例子就是JVM，Java通过JVM这一抽象层使得Java程序与操作系统和硬件平台解耦（因为每个Java程序都是运行在JVM中的），因此实现了所谓的compile once, run everywhere。</p>
</li>
</ul>
<p>Docker所用到的技术与上述两种都不相同，它使用了更轻量级的虚拟化技术，多个Container共享了同一个操作系统内核，并且就像运行在本地上一样。Container技术相对于虚拟机来说，只是一个应用程序层的抽象，它将代码与依赖关系打包到一起，多个Container可以在同一台机器上运行（意味着一个虚拟机上也可以运行多个Container），并与其它Container共享操作系统内核，每一个Container都在用户空间中作为一个独立的进程运行，这些特性都证明了Container要比虚拟机更加灵活与轻量（一般都是结合虚拟机与Docker一起使用）。</p>
<p><img src="http://wx3.sinaimg.cn/large/63503acbly1flnj7b3v1lj214x0d7mxs.jpg"></p>
<p>Container技术其实并不是个新鲜事物，最早可以追溯到UNIX中的chroot（在1979年的V7 Unix中引入），它可以改变当前正在运行的进程及其子目录的根目录，在这种修改过的环境下运行的程序不能在指定的目录树之外访问文件，从而限制用户的活动范围，为进程提供了隔离空间。</p>
<p>之后各种Unix版本涌现出很多Container技术，在2006年，Google提出了”Process Containers”期望在Linux内核中实现进程资源隔离的相关特性，由于Container在Linux内核中的定义过于宽泛混乱，后来该项目改名为CGroups（Control Groups），实现了对进程的资源限制。</p>
<p>2008年，LXC（Linux Containers）发布，它是一种在操作系统层级上的虚拟化方法，用于在Linux系统上通过共享一个内核来运行多个互相隔离的程序（Container）。LXC正是结合了Linux内核中的CGroups和对分离的名称空间的支持来为应用程序提供了一个隔离的环境。而Docker也是基于LXC实现的（Docker的前身是dotClound公司中的内部项目，它是一家提供PaaS服务的公司。），并作出了许多改进。</p>
<h3 id="使用Docker"><a href="#使用Docker" class="headerlink" title="使用Docker"></a>使用Docker</h3><hr>
<p>在使用Docker之前你需要先安装Docker（这好像是一句废话。。。），根据不同的平台安装方法都不相同，可以去参考<a target="_blank" rel="noopener" href="https://docs.docker.com/engine/installation/">Install Docker | Docker Documentation</a>或者自行Google。</p>
<p>安装完毕之后，输入<code>docker --version</code>来确认是否安装成功。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker --version</span></span><br><span class="line">Docker version 17.05.0-ce-rc1, build 2878a85</span><br></pre></td></tr></table></figure>
<p>从<a target="_blank" rel="noopener" href="https://hub.docker.com/">Docker Hub</a>中可以pull到其他人发布的Image，我们也可以注册一个账号去发布自己的Image与他人共享。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[root@Jack ~]# docker search redis # 查看redis镜像是否存在</span><br><span class="line">[root@Jack ~]# docker pull redis # 拉取redis镜像到本机</span><br><span class="line">Using default tag: latest</span><br><span class="line">Trying to pull repository docker.io/library/redis ... </span><br><span class="line">latest: Pulling from docker.io/library/redis</span><br><span class="line">Digest: sha256:cd277716dbff2c0211c8366687d275d2b53112fecbf9d6c86e9853edb0900956</span><br><span class="line">[root@Jack ~]# docker images # 查看镜像信息</span><br><span class="line">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">docker.io/python    3.6-onbuild         7195f9298ffb        2 weeks ago         691.1 MB</span><br><span class="line">docker.io/mongo     latest              d22888af0ce0        2 weeks ago         360.9 MB</span><br><span class="line">docker.io/redis     latest              8f2e175b3bd1        2 weeks ago         106.6 MB</span><br></pre></td></tr></table></figure>
<p>有了Image，之后就可以在其之上运行一个Container了，命令如下。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[root@Jack ~]# docker run -d -p 6379:6379 redis # 运行redis，-p代表将本机上6379端口映射到Container的6379端口 -d代表在后台启动</span><br><span class="line">[root@Jack ~]# docker ps -a # 查看容器信息，如果不加-a只会显示当前运行中的容器</span><br><span class="line"><span class="meta">#</span><span class="bash"> 如果想要进入容器中，那么需要执行以下命令</span></span><br><span class="line">[root@Jack ~]# docker ps # 先获得容器的id</span><br><span class="line">CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES</span><br><span class="line">1f928073b7eb        redis               &quot;docker-entrypoint.sh&quot;   45 seconds ago      Up 44 seconds       0.0.0.0:6379-&gt;6379/tcp   desperate_khorana</span><br><span class="line">[root@Jack ~]# docker exec -it 1f928073b7eb /bin/bash # 然后再执行该命令进入到容器中</span><br><span class="line">root@1f928073b7eb:/data# touch hello_docker.txt # 在容器中创建一个文件</span><br><span class="line">root@1f928073b7eb:/data# exit # 退出</span><br><span class="line">exit</span><br><span class="line">[root@Jack ~]# </span><br><span class="line"><span class="meta">#</span><span class="bash"> 也可以在启动时直接进入 命令如下</span></span><br><span class="line">[root@Jack ~]# docker run -d -it -p 6379:6379 redis /bin/bash</span><br></pre></td></tr></table></figure>
<p>我们对Container做出了修改，如果想要保留这个修改，可以通过commit命令来生成一个新的Image。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> -m为描述信息 -a为作者 1f9是你要保存的容器id 取前3个字符 docker可以自行识别</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> sylvanassun/redis为镜像名 :<span class="built_in">test</span> 为一个tag 一般用于标识版本</span></span><br><span class="line">[root@Jack ~]# docker commit -m &quot;test&quot; -a &quot;SylvanasSun&quot; 1f9 sylvanassun/redis:test</span><br><span class="line">sha256:e7073e8e5bd70b8d58092fd6bd8c2551e65dd29241c235eddf2a7f4b4b25cbbd</span><br><span class="line">[root@Jack ~]# docker images</span><br><span class="line">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE</span><br><span class="line">sylvanassun/redis   test                e7073e8e5bd7        2 seconds ago       106.6 MB</span><br><span class="line">docker.io/python    3.6-onbuild         7195f9298ffb        2 weeks ago         691.1 MB</span><br><span class="line">docker.io/mongo     latest              d22888af0ce0        2 weeks ago         360.9 MB</span><br><span class="line">docker.io/redis     latest              8f2e175b3bd1        2 weeks ago         106.6 MB</span><br></pre></td></tr></table></figure>
<p>想删除一个容器或镜像也很简单，但在删除镜像前需要先删除依赖于它的容器。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[root@Jack ~]# docker stop 1f9 # 关闭运行中的容器，相应的也有docker start id命令来启动一个容器</span><br><span class="line">1f9</span><br><span class="line">[root@Jack ~]# docker rm 1f9 # 删除容器</span><br><span class="line">1f9</span><br><span class="line">[root@Jack ~]# docker rmi e70 # 删除上面保存的镜像</span><br><span class="line">Untagged: sylvanassun/redis:test</span><br><span class="line">Deleted: sha256:e7073e8e5bd70b8d58092fd6bd8c2551e65dd29241c235eddf2a7f4b4b25cbbd</span><br><span class="line">Deleted: sha256:751db4a870e5f703082b31c1614a19c86e0c967334a61f5d22b2511072aef56d</span><br></pre></td></tr></table></figure>
<p>如果想要自己构建一个镜像，那么需要编写Dockerfile文件，该文件描述了镜像的依赖环境以及如何配置你的应用环境。</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用python:2.7-slim 作为父镜像</span></span><br><span class="line"><span class="keyword">FROM</span> python:<span class="number">2.7</span>-slim</span><br><span class="line"></span><br><span class="line"><span class="comment"># 跳转到/app 其实就是cd命令</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="bash"> /app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 将当前目录的内容(.)复制到镜像的/app目录下</span></span><br><span class="line"><span class="keyword">ADD</span><span class="bash"> . /app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># RUN代表运行的shell命令，下面这条命令是根据requirements.txt安装python应用的依赖包</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> pip install --trusted-host pypi.python.org -r requirements.txt</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 暴露80端口让外界访问</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">80</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义环境变量</span></span><br><span class="line"><span class="keyword">ENV</span> NAME World</span><br><span class="line"></span><br><span class="line"><span class="comment"># 当容器启动时执行的命令，它与RUN不同，只在容器启动时执行一次</span></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> [<span class="string">&quot;python&quot;</span>, <span class="string">&quot;app.py&quot;</span>]</span></span><br></pre></td></tr></table></figure>
<p>然后就可以通过<code>docker build -t xxx/xxxx .</code>命令来构建镜像，<code>-t</code>后面是镜像名与tag等信息，注意<code>.</code>表示在当前目录下寻找Dockerfile文件。</p>
<p>学会如何构建自己的镜像之后，你是否也想将它发布到<a target="_blank" rel="noopener" href="https://hub.docker.com/">Docker Hub</a>上与他人分享呢？要想做到这一点，需要先注册一个<a target="_blank" rel="noopener" href="https://hub.docker.com/">Docker Hub</a>账号，之后通过<code>docker login</code>命令登录，然后再<code>docker push image name</code>，就像在使用Git一样简单。</p>
<p>关于Docker的更多命令与使用方法，请参考<a target="_blank" rel="noopener" href="https://docs.docker.com/">Docker Documentation | Docker Documentation</a>，另外我还推荐使用<a target="_blank" rel="noopener" href="https://docs.docker.com/compose/">Docker Compose</a>来构建镜像，它可以很方便地组合管理多个镜像。</p>
<h3 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h3><hr>
<p>Docker提供了非常强大的自动化部署方式与灵活性，对多个应用程序之间做到了解耦，提供了开发上的敏捷性、可控性以及可移植性。同时，Docker也在不断地帮助越来越多的企业实现了向云端迁移、向微服务转型以及向DevOps模式的实践。</p>
<p>如今，微服务与DevOps火爆程度日益渐高，你又有何理由选择拒绝Docker呢？让我们一起选择拥抱Docker，拥抱未来！</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




  <nav class="pagination">
    <span class="page-number current">1</span><a class="page-number" href="/yuwanzi.io/page/2/">2</a><span class="space">&hellip;</span><a class="page-number" href="/yuwanzi.io/page/7/">7</a><a class="extend next" rel="next" href="/yuwanzi.io/page/2/"><i class="fa fa-angle-right" aria-label="Nächste Seite"></i></a>
  </nav>


<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      const activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      const commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>
</div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


<div class="copyright">
  &copy; 
  <span itemprop="copyrightYear">2021</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">玉丸子</span>
</div>
  <div class="powered-by">Erstellt mit  <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/muse/" class="theme-link" rel="noopener" target="_blank">NexT.Muse</a>
  </div>

    </div>
  </footer>

  
  <script src="//cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
<script src="/yuwanzi.io/js/utils.js"></script><script src="/yuwanzi.io/js/motion.js"></script><script src="/yuwanzi.io/js/schemes/muse.js"></script><script src="/yuwanzi.io/js/next-boot.js"></script>

  






  





</body>
</html>
